You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cb...@apache.org on 2017/06/08 11:42:09 UTC

[incubator-openwhisk] branch master updated: Reverting CLI changes to restore master

This is an automated email from the ASF dual-hosted git repository.

cbickel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new ade8699  Reverting CLI changes to restore master
ade8699 is described below

commit ade86999d58c1aeef4791f20c7116f02dedc2751
Author: Markus Thoemmes <ma...@de.ibm.com>
AuthorDate: Thu Jun 8 12:34:40 2017 +0200

    Reverting CLI changes to restore master
---
 ansible/files/package-versions.ini                 |    2 -
 ansible/group_vars/all                             |   44 -
 ansible/roles/cli/files/createContentJson.sh       |   69 -
 ansible/roles/cli/tasks/deploy.yml                 |   29 +-
 ansible/roles/cli/tasks/download_cli.yml           |    2 -
 ansible/roles/cli/tasks/download_openwhisk_cli.yml |   69 -
 tools/cli/Dockerfile                               |   40 +
 tools/cli/README.md                                |   15 +
 tools/cli/build.gradle                             |   93 ++
 tools/cli/build.sh                                 |  105 ++
 tools/cli/go-whisk-cli/Godeps/Godeps.json          |   85 ++
 tools/cli/go-whisk-cli/Godeps/Readme               |    5 +
 tools/cli/go-whisk-cli/commands/action.go          |  922 ++++++++++++
 tools/cli/go-whisk-cli/commands/activation.go      |  363 +++++
 tools/cli/go-whisk-cli/commands/api.go             | 1585 ++++++++++++++++++++
 tools/cli/go-whisk-cli/commands/commands.go        |  214 +++
 tools/cli/go-whisk-cli/commands/flags.go           |  131 ++
 tools/cli/go-whisk-cli/commands/namespace.go       |  126 ++
 tools/cli/go-whisk-cli/commands/package.go         |  581 +++++++
 tools/cli/go-whisk-cli/commands/property.go        |  486 ++++++
 tools/cli/go-whisk-cli/commands/rule.go            |  455 ++++++
 tools/cli/go-whisk-cli/commands/sdk.go             |  245 +++
 tools/cli/go-whisk-cli/commands/trigger.go         |  566 +++++++
 tools/cli/go-whisk-cli/commands/util.go            | 1069 +++++++++++++
 tools/cli/go-whisk-cli/commands/wsk.go             |   60 +
 tools/cli/go-whisk-cli/main.go                     |  105 ++
 tools/cli/go-whisk-cli/wski18n/detection.go        |   42 +
 tools/cli/go-whisk-cli/wski18n/i18n.go             |  147 ++
 .../go-whisk-cli/wski18n/resources/de_DE.all.json  |    0
 .../go-whisk-cli/wski18n/resources/en_US.all.json  | 1466 ++++++++++++++++++
 .../go-whisk-cli/wski18n/resources/es_ES.all.json  |    0
 .../go-whisk-cli/wski18n/resources/fr_FR.all.json  |    6 +
 .../go-whisk-cli/wski18n/resources/it_IT.all.json  |    0
 .../go-whisk-cli/wski18n/resources/ja_JA.all.json  |    0
 .../go-whisk-cli/wski18n/resources/ko_KR.all.json  |    0
 .../go-whisk-cli/wski18n/resources/pt_BR.all.json  |    0
 .../wski18n/resources/zh_Hans.all.json             |    0
 .../wski18n/resources/zh_Hant.all.json             |    0
 tools/cli/go-whisk/whisk/action.go                 |  213 +++
 tools/cli/go-whisk/whisk/activation.go             |  195 +++
 tools/cli/go-whisk/whisk/api.go                    |  586 ++++++++
 tools/cli/go-whisk/whisk/client.go                 |  600 ++++++++
 tools/cli/go-whisk/whisk/info.go                   |   68 +
 tools/cli/go-whisk/whisk/namespace.go              |  104 ++
 tools/cli/go-whisk/whisk/package.go                |  211 +++
 tools/cli/go-whisk/whisk/rule.go                   |  186 +++
 tools/cli/go-whisk/whisk/sdk.go                    |   74 +
 tools/cli/go-whisk/whisk/shared.go                 |   36 +
 tools/cli/go-whisk/whisk/trace.go                  |   93 ++
 tools/cli/go-whisk/whisk/trigger.go                |  188 +++
 tools/cli/go-whisk/whisk/util.go                   |   66 +
 tools/cli/go-whisk/whisk/wskerror.go               |  172 +++
 tools/cli/go-whisk/wski18n/detection.go            |   42 +
 tools/cli/go-whisk/wski18n/i18n.go                 |  147 ++
 .../cli/go-whisk/wski18n/resources/de_DE.all.json  |    0
 .../cli/go-whisk/wski18n/resources/en_US.all.json  |  126 ++
 .../cli/go-whisk/wski18n/resources/es_ES.all.json  |    0
 .../cli/go-whisk/wski18n/resources/fr_FR.all.json  |    0
 .../cli/go-whisk/wski18n/resources/it_IT.all.json  |    0
 .../cli/go-whisk/wski18n/resources/ja_JA.all.json  |    0
 .../cli/go-whisk/wski18n/resources/ko_KR.all.json  |    0
 .../cli/go-whisk/wski18n/resources/pt_BR.all.json  |    0
 .../go-whisk/wski18n/resources/zh_Hans.all.json    |    0
 .../go-whisk/wski18n/resources/zh_Hant.all.json    |    0
 64 files changed, 12033 insertions(+), 201 deletions(-)

diff --git a/ansible/files/package-versions.ini b/ansible/files/package-versions.ini
deleted file mode 100644
index 2464ea7..0000000
--- a/ansible/files/package-versions.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[openwhisk-cli]
-git_tag=latest
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index b0540f8..59ef946 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -1,7 +1,6 @@
 mode: deploy
 prompt_user: true
 openwhisk_home: "{{ lookup('env', 'OPENWHISK_HOME')|default(playbook_dir + '/..', true) }}"
-openwhisk_cli_home: "{{ lookup('env', 'OPENWHISK_CLI')|default(playbook_dir + '/../../incubator-openwhisk-cli', true) }}"
 exclude_logs_from: []
 
 # This whisk_api_localhost_name_default is used to configure nginx to permit vanity URLs for web actions
@@ -229,46 +228,3 @@ catalog_repos:
     location: "{{ openwhisk_home }}/../openwhisk-catalog"
     version: "HEAD"
     repo_update: "no"
-
-openwhisk_cli_tag: "{{ lookup('ini', 'git_tag section=openwhisk-cli file={{ openwhisk_home }}/ansible/files/package-versions.ini') }}"
-
-# The openwhisk_cli is used to determine how to install the OpenWhisk CLI. The
-# installation_mode can be specified into two modes: remote and local.
-# The mode remote means to download the available binaries from the releases page
-# of the official openwhisk cli repository. The mode local means to get the binaries
-# from a local directory. The default value for openwhisk is remote.
-#
-# The name specifies the package name of the binaries in remote mode.
-#
-# The dest_name specifies the package name of the binaries in Nginx in remote mode.
-#
-# The location specifies the official website where Openwhisk CLI is hosted in
-# remote mode or location to save the binaries of the OpenWhisk CLI in local mode.
-#
-
-openwhisk_cli:
-  installation_mode: remote
-  name: OpenWhisk_CLI
-  dest_name: OpenWhisk_CLI
-  location: "https://github.com/apache/incubator-openwhisk-cli/releases/download/{{ openwhisk_cli_tag }}"
-
-# If you would like to use the binaries available locally, e.g. the binaries generated in the development
-# environment, you can uncomment the following section to use the local mode. Please be advised that the
-# binaries are generated and the location is set correctly before running the ansible scripts.
-#
-#openwhisk_cli:
-#  installation_mode: local
-#  location: "{{ openwhisk_cli_home }}/bin"
-
-# The list of operating systems for which openwhisk cli binaries are downloaded,
-# if the installation_mode is remote.
-cli_os:
-  - linux
-  - mac
-  - windows
-
-# The list of architectures for which openwhisk cli binaries are downloaded,
-# if the installation_mode is remote.
-cli_arc:
-  - amd64
-  - 386
diff --git a/ansible/roles/cli/files/createContentJson.sh b/ansible/roles/cli/files/createContentJson.sh
deleted file mode 100755
index 6171568..0000000
--- a/ansible/roles/cli/files/createContentJson.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-
-set +x
-set -e
-
-get_compressed_name() {
-  local os=$1
-  local arch=$2
-  local product_name=$3
-
-  if [ $arch = amd64 ]; then
-    comp_name="$product_name-$os";
-  elif [ $arch = 386 ]; then
-    comp_name="$product_name-$os-32bit";
-  else
-    comp_name="$product_name-$os-$arch";
-  fi
-
-  echo $comp_name;
-};
-
-get_binary_path() {
-    local comp_name=$1
-    local os=$2
-    local arch=$3
-
-    if [ $os = "linux" ]; then
-        comp_name="$comp_name.tgz"
-    else
-    		comp_name="$comp_name.zip"
-    fi
-    echo $os/$arch/$comp_name;
-};
-
-create_cli_packages() {
-  local dirIndex="{\"cli\":{"
-
-  for platform in $platforms; do
-    dirIndex="$dirIndex\"$platform\":{"
-
-    for arch in $archs; do
-      comp_name=$(get_compressed_name $platform $arch $zip_name)
-      comp_path=$(get_binary_path $comp_name $platform $arch)
-
-      if [ $arch = $default_arch ]; then
-          dirIndex="$dirIndex\"default\":{\"path\":\"$comp_path\"},";
-      fi
-
-      dirIndex="$dirIndex\"$arch\":{\"path\":\"$comp_path\"},";
-    done
-
-    dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
-    dirIndex="$dirIndex},";
-  done
-
-  dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
-  dirIndex="$dirIndex}}"
-
-  echo $dirIndex > $PATH_CONTENT_JSON/content.json
-};
-
-archs="386 amd64";
-default_arch="amd64"
-
-PATH_CONTENT_JSON=$1
-BINARY_TAG=$2
-platforms=$3
-zip_name=$4
-create_cli_packages
diff --git a/ansible/roles/cli/tasks/deploy.yml b/ansible/roles/cli/tasks/deploy.yml
index eee6ead..1bf872d 100644
--- a/ansible/roles/cli/tasks/deploy.yml
+++ b/ansible/roles/cli/tasks/deploy.yml
@@ -6,24 +6,23 @@
     path: "{{ cli.nginxdir  }}"
     state: directory
 
-- set_fact:
-    cli_installation_mode="{{ openwhisk_cli.installation_mode }}"
-
 - include: docker_login.yml
 
-- name: "download the binaries from the release page of OpenWhisk CLI"
-  include: download_openwhisk_cli.yml
-  with_nested:
-    - "{{ cli_os }}"
-    - "{{ cli_arc }}"
-  when: cli_installation_mode == "remote"
+- name: "pull the {{ docker_image_tag }} image of cli"
+  shell: "docker pull {{ docker_registry }}{{ docker_image_prefix }}/cli:{{ docker_image_tag }}"
+  when: docker_registry != ""
+
+- name: "run Docker"
+  shell: "docker run --name cli {{ docker_registry }}{{ docker_image_prefix }}/cli:{{ docker_image_tag }}"
+
+- name: "copy all the cli binaries into nginx"
+  shell: "docker cp cli:/src/github.com/go-whisk-cli/build/. '{{ cli.nginxdir }}'"
+  ignore_errors: yes
 
-- name: "generate content.json for Nginx CLI directory"
-  local_action: shell "./createContentJson.sh" "{{ cli.nginxdir }}" "{{ openwhisk_cli_tag }}" "{{ cli_os | join(" ") }}" "{{ openwhisk_cli.dest_name }}" chdir="{{ openwhisk_home }}/ansible/roles/cli/files"
-  when: cli_installation_mode == "remote"
+- name: "stop Docker"
+  shell: "docker stop cli"
 
-- name: "copy the binaries from a local directory to Nginx"
-  copy: src={{ openwhisk_cli.location }}/ dest={{ cli.nginxdir }}/
-  when: cli_installation_mode == "local"
+- name: "remove cli container after publishing"
+  shell: "docker rm cli"
 
 - include: download_cli.yml
diff --git a/ansible/roles/cli/tasks/download_cli.yml b/ansible/roles/cli/tasks/download_cli.yml
index 27a2735..df18b3d 100644
--- a/ansible/roles/cli/tasks/download_cli.yml
+++ b/ansible/roles/cli/tasks/download_cli.yml
@@ -9,7 +9,6 @@
     dest="{{ openwhisk_home }}/bin/wsk"
     mode=0755
     validate_certs=False
-    force=True
   when: "'environments/mac' not in inventory_dir"
 
 - name: "download cli (mac) to openwhisk home at {{ openwhisk_home }}"
@@ -19,5 +18,4 @@
     dest="{{ openwhisk_home }}/bin/wsk"
     mode=0755
     validate_certs=False
-    force=True
   when: "'environments/mac' in inventory_dir"
diff --git a/ansible/roles/cli/tasks/download_openwhisk_cli.yml b/ansible/roles/cli/tasks/download_openwhisk_cli.yml
deleted file mode 100644
index bf55d6b..0000000
--- a/ansible/roles/cli/tasks/download_openwhisk_cli.yml
+++ /dev/null
@@ -1,69 +0,0 @@
----
-
-# Download the cli to Nginx CLI directory
-
-- name: "set the basic variables os, arc and suffix"
-  set_fact:
-    os="{{ item[0] }}"
-    arc="{{ item[1] }}"
-    suffix="zip"
-
-- name: "change the suffix to tgz if the operating system is Linux"
-  set_fact:
-    suffix="tgz"
-  when: os == "linux"
-
-- name: "set the file name to download"
-  set_fact:
-    source_name="{{ openwhisk_cli.name }}-{{ openwhisk_cli_tag }}-{{ os }}-{{ arc }}.{{ suffix }}"
-
-- name: "set the destination file name locally after download for 32 bit"
-  set_fact:
-    dest_name="{{ openwhisk_cli.dest_name }}-{{ os }}-32bit.{{ suffix }}"
-  when: arc == "386"
-
-- name: "set the destination file name locally after download for 64 bit"
-  set_fact:
-    dest_name="{{ openwhisk_cli.dest_name }}-{{ os }}.{{ suffix }}"
-  when: arc == "amd64"
-
-- name: "set the http header used to download the binary packages"
-  set_fact:
-    headers="{{ openwhisk_cli.headers }}"
-  when: openwhisk_cli.headers is defined
-
-- name: "set the http header to empty"
-  set_fact:
-    headers=""
-  when: openwhisk_cli.headers is not defined
-
-- name: "set the instance volume_dir"
-  set_fact:
-    instance: "{{instances | selectattr('name', 'equalto', 'db') | list | first}}"
-    volume_dir: "{{ instance.volume.fsmount | default( '/mnt/' + group_names|first, true ) }}:/usr/local/var/lib/couchdb"
-  when: (block_device is defined) and (block_device in disk_status.stdout)
-
-- name: "ensure Nginx cli directory for ({{ os }}) {{ arc }} exists"
-  file:
-    path: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}"
-    state: directory
-
-- name: "download cli ({{ os }}) {{ arc }} to Nginx cli directory"
-  get_url:
-    url: "{{ openwhisk_cli.location }}/{{ source_name }}"
-    dest: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/{{ dest_name }}"
-    headers: "{{ headers }}"
-    mode: 0755
-
-- name: "unarchive cli for Mac and Windows"
-  unarchive:
-    src: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/{{ dest_name }}"
-    dest: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/"
-    remote_src: True
-  when: os != "linux"
-
-- name: "unarchive cli for Linux"
-  command: tar -zxvf {{ dest_name }}
-  args:
-    chdir: "{{ cli.nginxdir }}/{{ os }}/{{ arc }}/"
-  when: os == "linux"
diff --git a/tools/cli/Dockerfile b/tools/cli/Dockerfile
new file mode 100644
index 0000000..186b4d5
--- /dev/null
+++ b/tools/cli/Dockerfile
@@ -0,0 +1,40 @@
+FROM golang:1.8
+
+# Install zip
+RUN apt-get -y update && \
+    apt-get -y install zip
+
+ENV GOPATH=/
+
+# Download and install tools
+RUN echo "Installing the godep tool"
+RUN go get github.com/tools/godep
+
+RUN echo "Installing the go-bindata tool"
+RUN go get github.com/jteeuwen/go-bindata/...
+
+ADD . /src/github.com/
+
+# Load all of the dependencies from the previously generated/saved godep generated godeps.json file
+RUN echo "Restoring Go dependencies"
+RUN cd /src/github.com/go-whisk-cli && /bin/godep restore -v
+
+# Collect all translated strings into single .go module
+RUN echo "Generating i18n Go module"
+RUN cd /src/github.com/go-whisk-cli && /bin/go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources
+RUN cd /src/github.com/go-whisk && /bin/go-bindata -pkg wski18n -o wski18n/i18n_resources.go wski18n/resources
+
+# Generate a Go package dependency list
+# NOTE: Currently, the 'go list' command will not work against the current Go CLI non-standard file structure
+#RUN cd /src/github.com/go-whisk-cli && go list -f '{{join .Deps "\n"}}' > ../../../wsk.deps.out
+RUN cd /src/github.com/go-whisk-cli && echo "Dependencies list requires restructuring the GO CLI packages" > ../../../wsk.deps.out
+
+# All of the Go CLI binaries will be placed under a build folder
+RUN mkdir /src/github.com/go-whisk-cli/build
+
+ARG CLI_OS
+ARG CLI_ARCH
+
+# Build the Go wsk CLI binaries and compress resultant binaries
+RUN chmod +x /src/github.com/build.sh
+RUN cd /src/github.com/ && ./build.sh
diff --git a/tools/cli/README.md b/tools/cli/README.md
new file mode 100644
index 0000000..44779d8
--- /dev/null
+++ b/tools/cli/README.md
@@ -0,0 +1,15 @@
+To build the OpenWhisk CLI run the following command from the OpenWhisk home directory:
+
+$ gradle :tools:cli:distDocker
+
+Multiple binaries are produced in a Docker container during the build process. One of those binaries is copied from the
+Docker container to the local file system in the following directory: ../../bin/wsk. This binary will be platform
+specific, it will only run on the operating system, and CPU architecture that matches the build machine.
+
+Currently the build process is only supported on Linux, and Mac operating systems running on an x86 CPU architecture.
+
+To get CLI command help, execute the following command:
+
+$ wsk --help
+
+To get CLI command debug information, include the -d, or --debug flag when executing a command.
diff --git a/tools/cli/build.gradle b/tools/cli/build.gradle
new file mode 100644
index 0000000..deda2a6
--- /dev/null
+++ b/tools/cli/build.gradle
@@ -0,0 +1,93 @@
+ext.dockerImageName = "cli"
+ext.dockerContainerName = "cli"
+ext.dockerBuildArgs = getDockerBuildArgs()
+apply from: '../../gradle/docker.gradle'
+
+
+task removeBinary(type: Delete) {
+    delete "${projectDir}/../../bin/wsk"
+    delete "${projectDir}/../../bin/mac"
+    delete "${projectDir}/../../bin/linux"
+    delete "${projectDir}/../../bin/windows"
+}
+
+task distBinary(dependsOn: [removeBinary, distDocker]) {
+    doLast {
+        run(dockerBinary + ["rm", "-f", dockerContainerName], true)
+        run(dockerBinary + ["run", "--name", dockerContainerName, dockerTaggedImageName])
+
+        // Copy all Go binaries from Docker into openwhisk/bin folder
+        run(dockerBinary + ["cp", dockerContainerName +
+                ":/src/github.com/go-whisk-cli/build/.", "${projectDir}/../../bin"])
+
+        run(dockerBinary + ["rm", "-f", dockerContainerName])
+    }
+}
+
+task dumpOSInfo {
+    doLast {
+        println "os.name = "+getOsName()
+        println "os.arch = "+getOsArch()
+        println "go.name = "+mapOsNameToGoName(getOsName())
+        println "go.arch = "+mapOsArchToGoArch(getOsArch())
+    }
+}
+
+task copyCLIShortcut(type: Copy, dependsOn: [distBinary, dumpOSInfo]) {
+    String go_osname     = mapOsNameToGoName(getOsName())
+    String go_osarch     = mapOsArchToGoArch(getOsArch())
+    String from_path_wsk = "${projectDir}/../../bin/${go_osname}/${go_osarch}/wsk"
+    String to_path_dir   = "${projectDir}/../../bin"
+
+    from from_path_wsk
+    into to_path_dir
+}
+
+pushImage.finalizedBy copyCLIShortcut
+
+// Returns the Go CLI docker build args
+def getDockerBuildArgs() {
+    String local_os = mapOsNameToGoName(getOsName())
+    String local_arch = mapOsArchToGoArch(getOsArch())
+    def res = []
+
+    if(!project.hasProperty('crossCompileCLI') || project.crossCompileCLI == "false") {
+        res = ["CLI_OS=${local_os}", "CLI_ARCH=${local_arch}"]
+    } else {
+        res = ["CLI_OS=mac linux windows", "CLI_ARCH=386 amd64"]
+    }
+
+    return res
+}
+
+def run(cmd, ignoreError = false) {
+    println("Executing '${cmd.join(" ")}'")
+    def proc = cmd.execute()
+    proc.waitFor()
+    if(!ignoreError && proc.exitValue() != 0) {
+        println("Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}")
+    }
+}
+
+def getOsName() {
+    return System.properties['os.name']
+}
+
+def getOsArch() {
+    return System.properties['os.arch']
+}
+
+def mapOsNameToGoName(String osname) {
+    String osname_l = osname.toLowerCase()
+    if (osname_l.contains("nux") || osname.contains("nix")) return "linux"
+    if (osname_l.contains("mac")) return "mac"
+    if (osname_l.contains("windows")) return "windows"
+    return osname_l
+}
+
+def mapOsArchToGoArch(String osarch) {
+    String osarch_l = osarch.toLowerCase()
+    if (osarch_l.contains("x86_64") || osarch_l == "amd64") return "amd64"
+    if (osarch_l.contains("i386") || osarch_l.contains("x86_32")) return "386"
+    return osarch_l
+}
diff --git a/tools/cli/build.sh b/tools/cli/build.sh
new file mode 100644
index 0000000..f4aeb42
--- /dev/null
+++ b/tools/cli/build.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+set +x
+set -e
+
+get_bin_name () {
+  local os=$1
+  local bin="wsk"
+
+  if [ $os = "windows" ]; then
+    bin="${bin}.exe";
+  fi
+
+  echo $bin;
+};
+
+build_cli () {
+  local os=$1
+  local arch=$2
+  local bin=$3
+
+  echo "Building for OS '$os' and architecture '$arch'"
+
+  if [ $os = "mac" ]; then
+    export GOOS=darwin;
+  else
+    export GOOS=$os;
+  fi
+
+  export GOARCH=$arch
+
+  cd /src/github.com/go-whisk-cli
+  go build -ldflags "-X main.CLI_BUILD_TIME=`date -u '+%Y-%m-%dT%H:%M:%S%:z'`" -v -o build/$os/$arch/$bin main.go;
+};
+
+get_compressed_name() {
+  local os=$1
+  local arch=$2
+  local product_name="OpenWhisk_CLI"
+
+  if [ $arch = amd64 ]; then
+      comp_name="$product_name-$os";
+  elif [ $arch = 386 ]; then
+      comp_name="$product_name-$os-32bit";
+  else
+      comp_name="$product_name-$os-$arch";
+  fi
+
+  echo $comp_name;
+};
+
+compress_binary() {
+    local comp_name=$1
+    local bin=$2
+    local os=$3
+    local arch=$4
+
+    cd build/$os/$arch
+
+    if [ $os = "linux" ]; then
+      comp_name="$comp_name.tgz"
+      tar -cvzf $comp_name $bin >/dev/null 2>&1;
+    else
+      comp_name="$comp_name.zip"
+      zip $comp_name $bin >/dev/null 2>&1;
+    fi
+
+    cd ../../..
+    echo $os/$arch/$comp_name;
+};
+
+create_cli_packages() {
+  local dirIndex="{\"cli\":{"
+
+  for platform in $platforms; do
+    dirIndex="$dirIndex\"$platform\":{"
+
+    for arch in $archs; do
+      bin=$(get_bin_name $platform)
+      build_cli $platform $arch $bin
+      comp_name=$(get_compressed_name $platform $arch)
+      comp_path=$(compress_binary $comp_name $bin $platform $arch)
+
+      if [ $arch = $default_arch ]; then
+          dirIndex="$dirIndex\"default\":{\"path\":\"$comp_path\"},";
+      fi
+
+      dirIndex="$dirIndex\"$arch\":{\"path\":\"$comp_path\"},";
+    done
+
+    dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
+    dirIndex="$dirIndex},";
+  done
+
+  dirIndex="$(echo $dirIndex | rev | cut -c2- | rev)"
+  dirIndex="$dirIndex}}"
+
+  echo $dirIndex > ./build/content.json
+};
+
+platforms="$CLI_OS"
+archs="$CLI_ARCH";
+default_arch="amd64"
+
+create_cli_packages
diff --git a/tools/cli/go-whisk-cli/Godeps/Godeps.json b/tools/cli/go-whisk-cli/Godeps/Godeps.json
new file mode 100644
index 0000000..ab146c9
--- /dev/null
+++ b/tools/cli/go-whisk-cli/Godeps/Godeps.json
@@ -0,0 +1,85 @@
+{
+	"ImportPath": "go-whisk-cli",
+	"GoVersion": "go1.6",
+	"GodepVersion": "v74",
+	"Packages": [
+		"go-whisk-cli"
+	],
+	"Deps": [
+		{
+			"ImportPath": "github.com/cloudfoundry/jibber_jabber",
+			"Rev": "bcc4c8345a21301bf47c032ff42dd1aae2fe3027"
+		},
+		{
+			"ImportPath": "github.com/fatih/color",
+			"Comment": "v0.1-19-g87d4004",
+			"Rev": "87d4004f2ab62d0d255e0a38f1680aa534549fe3"
+		},
+		{
+			"ImportPath": "github.com/google/go-querystring/query",
+			"Rev": "9235644dd9e52eeae6fa48efd539fdc351a0af53"
+		},
+		{
+			"ImportPath": "github.com/hokaccha/go-prettyjson",
+			"Rev": "f75235bd99dad4e98ff360db8372d5c0ef1d054a"
+		},
+		{
+			"ImportPath": "github.com/inconshreveable/mousetrap",
+			"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
+		},
+		{
+			"ImportPath": "github.com/mattn/go-colorable",
+			"Comment": "v0.0.5",
+			"Rev": "9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59"
+		},
+		{
+			"ImportPath": "github.com/mattn/go-isatty",
+			"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
+		},
+		{
+			"ImportPath": "github.com/mitchellh/go-homedir",
+			"Rev": "1111e456ffea841564ac0fa5f69c26ef44dafec9"
+		},
+		{
+			"ImportPath": "github.com/nicksnyder/go-i18n/i18n",
+			"Comment": "v1.4.0",
+			"Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+		},
+		{
+			"ImportPath": "github.com/nicksnyder/go-i18n/i18n/bundle",
+			"Comment": "v1.4.0",
+			"Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+		},
+		{
+			"ImportPath": "github.com/nicksnyder/go-i18n/i18n/language",
+			"Comment": "v1.4.0",
+			"Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+		},
+		{
+			"ImportPath": "github.com/nicksnyder/go-i18n/i18n/translation",
+			"Comment": "v1.4.0",
+			"Rev": "37e5c2de3e03e4b82693e3fcb4a6aa2cc4eb07e3"
+		},
+		{
+			"ImportPath": "github.com/spf13/cobra",
+			"Rev": "1238ba19d24b0b9ceee2094e1cb31947d45c3e86"
+		},
+		{
+			"ImportPath": "github.com/spf13/pflag",
+			"Rev": "367864438f1b1a3c7db4da06a2f55b144e6784e0"
+		},
+		{
+			"ImportPath": "golang.org/x/sys/unix",
+			"Rev": "7f918dd405547ecb864d14a8ecbbfe205b5f930f"
+		},
+		{
+			"ImportPath": "gopkg.in/yaml.v2",
+			"Rev": "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b"
+		},
+		{
+			"ImportPath": "github.com/ghodss/yaml",
+			"Comment": "v1.0.0",
+			"Rev": "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
+		}
+	]
+}
\ No newline at end of file
diff --git a/tools/cli/go-whisk-cli/Godeps/Readme b/tools/cli/go-whisk-cli/Godeps/Readme
new file mode 100644
index 0000000..4cdaa53
--- /dev/null
+++ b/tools/cli/go-whisk-cli/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/tools/cli/go-whisk-cli/commands/action.go b/tools/cli/go-whisk-cli/commands/action.go
new file mode 100644
index 0000000..6e2a558
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/action.go
@@ -0,0 +1,922 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "encoding/base64"
+    "errors"
+    "fmt"
+    "path/filepath"
+    "io"
+    "strings"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/fatih/color"
+    "github.com/spf13/cobra"
+    "github.com/mattn/go-colorable"
+)
+
+const MEMORY_LIMIT = 256
+const TIMEOUT_LIMIT = 60000
+const LOGSIZE_LIMIT = 10
+const ACTIVATION_ID = "activationId"
+const WEB_EXPORT_ANNOT = "web-export"
+const RAW_HTTP_ANNOT = "raw-http"
+const FINAL_ANNOT = "final"
+
+var actionCmd = &cobra.Command{
+    Use:   "action",
+    Short: wski18n.T("work with actions"),
+}
+
+var actionCreateCmd = &cobra.Command{
+    Use:           "create ACTION_NAME ACTION",
+    Short:         wski18n.T("create a new action"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var action *whisk.Action
+        var err error
+
+        if whiskErr := checkArgs(
+            args,
+            2,
+            2,
+            "Action create",
+            wski18n.T("An action name and action are required.")); whiskErr != nil {
+                return whiskErr
+        }
+
+        if action, err = parseAction(cmd, args, false); err != nil {
+            return actionParseError(cmd, args, err)
+        }
+
+        if _, _, err = client.Actions.Insert(action, false); err != nil {
+            return actionInsertError(action, err)
+        }
+
+        printActionCreated(action.Name)
+
+        return nil
+    },
+}
+
+var actionUpdateCmd = &cobra.Command{
+    Use:           "update ACTION_NAME [ACTION]",
+    Short:         wski18n.T("update an existing action, or create an action if it does not exist"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var action *whisk.Action
+        var err error
+
+        if whiskErr := checkArgs(
+            args,
+            1,
+            2,
+            "Action update",
+            wski18n.T("An action name is required. An action is optional.")); whiskErr != nil {
+                return whiskErr
+        }
+
+        if action, err = parseAction(cmd, args, false); err != nil {
+            return actionParseError(cmd, args, err)
+        }
+
+        if _, _, err = client.Actions.Insert(action, true); err != nil {
+            return actionInsertError(action, err)
+        }
+
+        printActionUpdated(action.Name)
+
+        return nil
+    },
+}
+
+var actionInvokeCmd = &cobra.Command{
+    Use:           "invoke ACTION_NAME",
+    Short:         wski18n.T("invoke action"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var parameters interface{}
+        var qualifiedName QualifiedName
+        var paramArgs []string
+
+        if whiskErr := checkArgs(
+            args,
+            1,
+            1,
+            "Action invoke",
+            wski18n.T("An action name is required.")); whiskErr != nil {
+                return whiskErr
+        }
+
+        if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+            return parseQualifiedNameError(args[0], err)
+        }
+
+        client.Namespace = qualifiedName.namespace
+        paramArgs = flags.common.param
+
+        if len(paramArgs) > 0 {
+            if parameters, err = getJSONFromStrings(paramArgs, false); err != nil {
+                return getJSONFromStringsParamError(paramArgs, false, err)
+            }
+        }
+        if flags.action.result {flags.common.blocking = true}
+
+        res, _, err := client.Actions.Invoke(
+            qualifiedName.entityName,
+            parameters,
+            flags.common.blocking,
+            flags.action.result)
+
+        return handleInvocationResponse(qualifiedName, parameters, res, err)
+    },
+}
+
+func handleInvocationResponse(
+    qualifiedName QualifiedName,
+    parameters interface{},
+    result map[string]interface{},
+    err error) (error) {
+        if err == nil {
+            printInvocationMsg(
+                qualifiedName.namespace,
+                qualifiedName.entityName,
+                getValueFromJSONResponse(ACTIVATION_ID, result),
+                result,
+                color.Output)
+        } else {
+            if !flags.common.blocking {
+                return handleInvocationError(err, qualifiedName.entityName, parameters)
+            } else {
+                if isBlockingTimeout(err) {
+                    printBlockingTimeoutMsg(
+                        qualifiedName.namespace,
+                        qualifiedName.entityName,
+                        getValueFromJSONResponse(ACTIVATION_ID, result))
+                } else if isApplicationError(err) {
+                    printInvocationMsg(
+                        qualifiedName.namespace,
+                        qualifiedName.entityName,
+                        getValueFromJSONResponse(ACTIVATION_ID, result),
+                        result,
+                        colorable.NewColorableStderr())
+                } else {
+                    return handleInvocationError(err, qualifiedName.entityName, parameters)
+                }
+            }
+        }
+
+        return err
+}
+
+var actionGetCmd = &cobra.Command{
+    Use:           "get ACTION_NAME [FIELD_FILTER]",
+    Short:         wski18n.T("get action"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var field string
+        var action *whisk.Action
+        var qualifiedName QualifiedName
+
+        if whiskErr := checkArgs(args, 1, 2, "Action get", wski18n.T("An action name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if len(args) > 1 {
+            field = args[1]
+
+            if !fieldExists(&whisk.Action{}, field) {
+                return invalidFieldFilterError(field)
+            }
+        }
+
+        if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+            return parseQualifiedNameError(args[0], err)
+        }
+
+        client.Namespace = qualifiedName.namespace
+
+        if action, _, err = client.Actions.Get(qualifiedName.entityName); err != nil {
+            return actionGetError(qualifiedName.entityName, err)
+        }
+
+        if flags.common.summary {
+            printSummary(action)
+        } else {
+            if len(field) > 0 {
+                printActionGetWithField(qualifiedName.entityName, field, action)
+            } else {
+                printActionGet(qualifiedName.entityName, action)
+            }
+        }
+
+        return nil
+    },
+}
+
+var actionDeleteCmd = &cobra.Command{
+    Use:           "delete ACTION_NAME",
+    Short:         wski18n.T("delete action"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var qualifiedName QualifiedName
+        var err error
+
+        if whiskErr := checkArgs(
+            args,
+            1,
+            1,
+            "Action delete",
+            wski18n.T("An action name is required.")); whiskErr != nil {
+                return whiskErr
+        }
+
+        if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+            return parseQualifiedNameError(args[0], err)
+        }
+
+        client.Namespace = qualifiedName.namespace
+
+        if _, err = client.Actions.Delete(qualifiedName.entityName); err != nil {
+            return actionDeleteError(qualifiedName.entityName, err)
+        }
+
+        printActionDeleted(qualifiedName.entityName)
+
+        return nil
+    },
+}
+
+var actionListCmd = &cobra.Command{
+    Use:           "list [NAMESPACE]",
+    Short:         wski18n.T("list all actions"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var qualifiedName QualifiedName
+        var actions []whisk.Action
+        var err error
+
+        if len(args) == 1 {
+            if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+                return parseQualifiedNameError(args[0], err)
+            }
+
+            client.Namespace = qualifiedName.namespace
+        } else if whiskErr := checkArgs(
+            args,
+            0,
+            1,
+            "Action list",
+            wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        options := &whisk.ActionListOptions{
+            Skip:  flags.common.skip,
+            Limit: flags.common.limit,
+        }
+
+        if actions, _, err = client.Actions.List(qualifiedName.entityName, options); err != nil {
+            return actionListError(qualifiedName.entityName, options, err)
+        }
+
+        printList(actions)
+
+        return nil
+    },
+}
+
+func parseAction(cmd *cobra.Command, args []string, update bool) (*whisk.Action, error) {
+    var err error
+    var artifact string
+    var existingAction *whisk.Action
+    var paramArgs []string
+    var annotArgs []string
+    var parameters interface{}
+    var annotations interface{}
+
+    qualifiedName := QualifiedName{}
+
+    if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
+        return nil, parseQualifiedNameError(args[0], err)
+    }
+
+    if len(args) == 2 {
+        artifact = args[1]
+    }
+
+    client.Namespace = qualifiedName.namespace
+    action := new(whisk.Action)
+    action.Name = qualifiedName.entityName
+    action.Namespace = qualifiedName.namespace
+    action.Limits = getLimits(
+        cmd.LocalFlags().Changed(MEMORY_FLAG),
+        cmd.LocalFlags().Changed(LOG_SIZE_FLAG),
+        cmd.LocalFlags().Changed(TIMEOUT_FLAG),
+        flags.action.memory,
+        flags.action.logsize,
+        flags.action.timeout)
+
+    paramArgs = flags.common.param
+    annotArgs = flags.common.annotation
+
+    if len(paramArgs) > 0 {
+        if parameters, err = getJSONFromStrings(paramArgs, true); err != nil {
+            return nil, getJSONFromStringsParamError(paramArgs, true, err)
+        }
+
+        action.Parameters = parameters.(whisk.KeyValueArr)
+    }
+
+    if len(annotArgs) > 0 {
+        if annotations, err = getJSONFromStrings(annotArgs, true); err != nil {
+            return nil, getJSONFromStringsAnnotError(annotArgs, true, err)
+        }
+
+        action.Annotations = annotations.(whisk.KeyValueArr)
+    }
+
+    if flags.action.copy {
+        copiedQualifiedName := QualifiedName{}
+
+        if copiedQualifiedName, err = parseQualifiedName(args[1]); err != nil {
+            return nil, parseQualifiedNameError(args[1], err)
+        }
+
+        client.Namespace = copiedQualifiedName.namespace
+
+        if existingAction, _, err = client.Actions.Get(copiedQualifiedName.entityName); err != nil {
+            return nil, actionGetError(copiedQualifiedName.entityName, err)
+        }
+
+        client.Namespace = qualifiedName.namespace
+        action.Exec = existingAction.Exec
+        action.Parameters = append(action.Parameters, existingAction.Parameters...)
+        action.Annotations = append(action.Annotations, existingAction.Annotations...)
+    } else if flags.action.sequence {
+        action.Exec = new(whisk.Exec)
+        action.Exec.Kind = "sequence"
+        action.Exec.Components = csvToQualifiedActions(artifact)
+    } else if len(artifact) > 0 {
+        action.Exec, err = getExec(args[1], flags.action.kind, flags.action.docker, flags.action.main)
+    }
+
+    if cmd.LocalFlags().Changed(WEB_FLAG) {
+        action.Annotations, err = webAction(flags.action.web, action.Annotations, qualifiedName.entityName, update)
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Parsed action struct: %#v\n", action)
+
+    return action, err
+}
+
+func getExec(artifact string, kind string, isDocker bool, mainEntry string) (*whisk.Exec, error) {
+    var err error
+    var code string
+    var exec *whisk.Exec
+
+    ext := filepath.Ext(artifact)
+    exec = new(whisk.Exec)
+
+    if !isDocker || ext == ".zip" {
+        code, err = readFile(artifact)
+
+        if ext == ".zip" || ext == ".jar" {
+            // Base64 encode the file
+            code = base64.StdEncoding.EncodeToString([]byte(code))
+            exec.Code = &code
+        } else {
+            exec.Code = &code
+        }
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", artifact, err)
+            return nil, err
+        }
+    }
+
+    if len(kind) > 0 {
+        exec.Kind = kind
+    } else if isDocker {
+        exec.Kind = "blackbox"
+        if ext != ".zip" {
+            exec.Image = artifact
+        } else {
+            exec.Image = "openwhisk/dockerskeleton"
+        }
+    } else if ext == ".swift" {
+        exec.Kind = "swift:default"
+    } else if ext == ".js" {
+        exec.Kind = "nodejs:default"
+    } else if ext == ".py" {
+        exec.Kind = "python:default"
+    } else if ext == ".jar" {
+        exec.Kind = "java:default"
+    } else {
+        if ext == ".zip" {
+            return nil, zipKindError()
+        } else {
+            return nil, extensionError(ext)
+        }
+    }
+
+    // Error if entry point is not specified for Java
+    if len(mainEntry) != 0 {
+        exec.Main = mainEntry
+    } else {
+        if exec.Kind == "java" {
+            return nil, javaEntryError()
+        }
+    }
+
+    return exec, nil
+}
+
+func webAction(webMode string, annotations whisk.KeyValueArr, entityName string, fetch bool) (whisk.KeyValueArr, error){
+    switch strings.ToLower(webMode) {
+    case "yes":
+        fallthrough
+    case "true":
+        return webActionAnnotations(fetch, annotations, entityName, addWebAnnotations)
+    case "no":
+        fallthrough
+    case "false":
+        return webActionAnnotations(fetch, annotations, entityName, deleteWebAnnotations)
+    case "raw":
+        return webActionAnnotations(fetch, annotations, entityName, addRawAnnotations)
+    default:
+        return nil, webInputError(webMode)
+    }
+}
+
+type WebActionAnnotationMethod func(annotations whisk.KeyValueArr) (whisk.KeyValueArr)
+
+func webActionAnnotations(
+    fetchAnnotations bool,
+    annotations whisk.KeyValueArr,
+    entityName string,
+    webActionAnnotationMethod WebActionAnnotationMethod) (whisk.KeyValueArr, error) {
+        var action *whisk.Action
+        var err error
+
+        if annotations != nil || !fetchAnnotations {
+            annotations = webActionAnnotationMethod(annotations)
+        } else {
+            if action, _, err = client.Actions.Get(entityName); err != nil {
+                return nil, actionGetError(entityName, err)
+            } else {
+                annotations = webActionAnnotationMethod(action.Annotations)
+            }
+        }
+
+        return annotations, nil
+}
+
+func addWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+    annotations = deleteWebAnnotationKeys(annotations)
+    annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
+    annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
+    annotations = addKeyValue(FINAL_ANNOT, true, annotations)
+
+    return annotations
+}
+
+func deleteWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+    annotations = deleteWebAnnotationKeys(annotations)
+    annotations = addKeyValue(WEB_EXPORT_ANNOT, false, annotations)
+    annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
+    annotations = addKeyValue(FINAL_ANNOT, false, annotations)
+
+    return annotations
+}
+
+func addRawAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+    annotations = deleteWebAnnotationKeys(annotations)
+    annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
+    annotations = addKeyValue(RAW_HTTP_ANNOT, true, annotations)
+    annotations = addKeyValue(FINAL_ANNOT, true, annotations)
+
+    return annotations
+}
+
+func deleteWebAnnotationKeys(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
+    annotations = deleteKey(WEB_EXPORT_ANNOT, annotations)
+    annotations = deleteKey(RAW_HTTP_ANNOT, annotations)
+    annotations = deleteKey(FINAL_ANNOT, annotations)
+
+    return annotations
+}
+
+func getLimits(memorySet bool, logSizeSet bool, timeoutSet bool, memory int, logSize int, timeout int) (*whisk.Limits) {
+    var limits *whisk.Limits
+
+    if memorySet || logSizeSet || timeoutSet {
+        limits = new(whisk.Limits)
+
+        if memorySet {
+            limits.Memory = &memory
+        }
+
+        if logSizeSet {
+            limits.Logsize = &logSize
+        }
+
+        if timeoutSet {
+            limits.Timeout = &timeout
+        }
+    }
+
+    return limits
+}
+
+func nestedError(errorMessage string, err error) (error) {
+    return whisk.MakeWskErrorFromWskError(
+        errors.New(errorMessage),
+        err,
+        whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG,
+        whisk.DISPLAY_USAGE)
+}
+
+func nonNestedError(errorMessage string) (error) {
+    return whisk.MakeWskError(
+        errors.New(errorMessage),
+        whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG,
+        whisk.DISPLAY_USAGE)
+}
+
+func actionParseError(cmd *cobra.Command, args []string, err error) (error) {
+    whisk.Debug(whisk.DbgError, "parseAction(%s, %s) error: %s\n", cmd, args, err)
+
+    errMsg := wski18n.T(
+        "Unable to parse action command arguments: {{.err}}",
+        map[string]interface{}{
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func actionInsertError(action *whisk.Action, err error) (error) {
+    whisk.Debug(whisk.DbgError, "client.Actions.Insert(%#v, false) error: %s\n", action, err)
+
+    errMsg := wski18n.T(
+        "Unable to create action '{{.name}}': {{.err}}",
+        map[string]interface{}{
+            "name": action.Name,
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func parseQualifiedNameError(entityName string, err error) (error) {
+    whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", entityName, err)
+
+    errMsg := wski18n.T(
+        "'{{.name}}' is not a valid qualified name: {{.err}}",
+        map[string]interface{}{
+            "name": entityName,
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func getJSONFromStringsParamError(params []string, keyValueFormat bool, err error) (error) {
+    whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", params, keyValueFormat, err)
+
+    errMsg := wski18n.T(
+        "Invalid parameter argument '{{.param}}': {{.err}}",
+        map[string]interface{}{
+            "param": fmt.Sprintf("%#v", params),
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func getJSONFromStringsAnnotError(annots []string, keyValueFormat bool, err error) (error) {
+    whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", annots, keyValueFormat, err)
+
+    errMsg := wski18n.T(
+        "Invalid annotation argument '{{.annotation}}': {{.err}}",
+        map[string]interface{}{
+            "annotation": fmt.Sprintf("%#v", annots),
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func invalidFieldFilterError(field string) (error) {
+    errMsg := wski18n.T(
+        "Invalid field filter '{{.arg}}'.",
+        map[string]interface{}{
+            "arg": field,
+        })
+
+    return nonNestedError(errMsg)
+}
+
+func actionDeleteError(entityName string, err error) (error) {
+    whisk.Debug(whisk.DbgError, "client.Actions.Delete(%s) error: %s\n", entityName, err)
+
+    errMsg := wski18n.T(
+        "Unable to delete action '{{.name}}': {{.err}}",
+        map[string]interface{}{
+            "name": entityName,
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func actionGetError(entityName string, err error) (error) {
+    whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", entityName, err)
+
+    errMsg := wski18n.T(
+        "Unable to get action '{{.name}}': {{.err}}",
+        map[string]interface{}{
+            "name": entityName,
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func handleInvocationError(err error, entityName string, parameters interface{}) (error) {
+    whisk.Debug(
+        whisk.DbgError,
+        "client.Actions.Invoke(%s, %s, %t) error: %s\n",
+        entityName, parameters,
+        flags.common.blocking,
+        err)
+
+    errMsg := wski18n.T(
+        "Unable to invoke action '{{.name}}': {{.err}}",
+        map[string]interface{}{
+            "name": entityName,
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func actionListError(entityName string, options *whisk.ActionListOptions, err error) (error) {
+    whisk.Debug(whisk.DbgError, "client.Actions.List(%s, %#v) error: %s\n", entityName, options, err)
+
+    errMsg := wski18n.T(
+        "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}",
+        map[string]interface{}{
+            "name": getClientNamespace(),
+            "err": err,
+        })
+
+    return nestedError(errMsg, err)
+}
+
+func webInputError(arg string) (error) {
+    errMsg := wski18n.T(
+        "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'.",
+        map[string]interface{}{
+            "arg": arg,
+        })
+
+    return nonNestedError(errMsg)
+}
+
+func zipKindError() (error) {
+    errMsg := wski18n.T("creating an action from a .zip artifact requires specifying the action kind explicitly")
+
+    return nonNestedError(errMsg)
+}
+
+func extensionError(extension string) (error) {
+    errMsg := wski18n.T(
+        "'{{.name}}' is not a supported action runtime",
+        map[string]interface{}{
+            "name": extension,
+        })
+
+    return nonNestedError(errMsg)
+}
+
+func javaEntryError() (error) {
+    errMsg := wski18n.T("Java actions require --main to specify the fully-qualified name of the main class")
+
+    return nonNestedError(errMsg)
+}
+
+func printActionCreated(entityName string) {
+    fmt.Fprintf(
+        color.Output,
+        wski18n.T(
+            "{{.ok}} created action {{.name}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "name": boldString(entityName),
+            }))
+}
+
+func printActionUpdated(entityName string) {
+    fmt.Fprintf(
+        color.Output,
+        wski18n.T(
+            "{{.ok}} updated action {{.name}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "name": boldString(entityName),
+            }))
+}
+
+func printBlockingTimeoutMsg(namespace string, entityName string, activationID interface{}) {
+    fmt.Fprintf(
+        colorable.NewColorableStderr(),
+        wski18n.T(
+            "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "namespace": boldString(namespace),
+                "name": boldString(entityName),
+                "id": boldString(activationID),
+            }))
+}
+
+func printInvocationMsg(
+    namespace string,
+    entityName string,
+    activationID interface{},
+    response map[string]interface{},
+    outputStream io.Writer) {
+        if !flags.action.result {
+            fmt.Fprintf(
+                outputStream,
+                wski18n.T(
+                    "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "namespace": boldString(namespace),
+                        "name": boldString(entityName),
+                        "id": boldString(activationID),
+                    }))
+        }
+
+        if flags.common.blocking {
+            printJSON(response, outputStream)
+        }
+}
+
+func printActionGetWithField(entityName string, field string, action *whisk.Action) {
+    fmt.Fprintf(
+        color.Output,
+        wski18n.T(
+            "{{.ok}} got action {{.name}}, displaying field {{.field}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "name": boldString(entityName),
+                "field": boldString(field),
+            }))
+
+    printField(action, field)
+}
+
+func printActionGet(entityName string, action *whisk.Action) {
+    fmt.Fprintf(
+        color.Output,
+        wski18n.T("{{.ok}} got action {{.name}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "name": boldString(entityName),
+            }))
+
+    printJSON(action)
+}
+
+func printActionDeleted(entityName string) {
+    fmt.Fprintf(
+        color.Output,
+        wski18n.T(
+            "{{.ok}} deleted action {{.name}}\n",
+            map[string]interface{}{
+                "ok": color.GreenString("ok:"),
+                "name": boldString(entityName),
+            }))
+}
+
+// Check if the specified action is a web-action
+func isWebAction(client *whisk.Client, qname QualifiedName) error {
+    var err error = nil
+
+    savedNs := client.Namespace
+    client.Namespace = qname.namespace
+    fullActionName := "/" + qname.namespace + "/" + qname.entityName
+
+    action, _, err := client.Actions.Get(qname.entityName)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", fullActionName, err)
+        whisk.Debug(whisk.DbgError, "Unable to obtain action '%s' for web action validation\n", fullActionName)
+        err = errors.New(wski18n.T("API action '{{.name}}' does not exist", map[string]interface{}{"name": fullActionName}))
+    } else {
+        err = errors.New(wski18n.T("API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action.",
+            map[string]interface{}{"name": fullActionName}))
+        weVal := getValue(action.Annotations, "web-export")
+        if (weVal == nil) {
+            whisk.Debug(whisk.DbgError, "getValue(annotations, web-export) for action %s found no value\n", fullActionName)
+        } else {
+            var webExport bool
+            var ok bool
+            if webExport, ok = weVal.(bool); !ok {
+                whisk.Debug(whisk.DbgError, "web-export annotation value (%v) is not a boolean\n", weVal)
+            } else if !webExport {
+                whisk.Debug(whisk.DbgError, "web-export annotation value is false\n", weVal)
+            } else {
+                err = nil
+            }
+        }
+    }
+
+    client.Namespace = savedNs
+    return err
+}
+
+func init() {
+    actionCreateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
+    actionCreateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
+    actionCreateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
+    actionCreateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
+    actionCreateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
+    actionCreateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
+    actionCreateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
+    actionCreateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
+    actionCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", nil, wski18n.T("annotation values in `KEY VALUE` format"))
+    actionCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+    actionCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", nil, wski18n.T("parameter values in `KEY VALUE` format"))
+    actionCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+    actionCreateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
+
+    actionUpdateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
+    actionUpdateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
+    actionUpdateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
+    actionUpdateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
+    actionUpdateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
+    actionUpdateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
+    actionUpdateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
+    actionUpdateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
+    actionUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+    actionUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+    actionUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+    actionUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+    actionUpdateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
+
+    actionInvokeCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+    actionInvokeCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+    actionInvokeCmd.Flags().BoolVarP(&flags.common.blocking, "blocking", "b", false, wski18n.T("blocking invoke"))
+    actionInvokeCmd.Flags().BoolVarP(&flags.action.result, "result", "r", false, wski18n.T("blocking invoke; show only activation result (unless there is a failure)"))
+
+    actionGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize action details"))
+
+    actionListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+    actionListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+
+    actionCmd.AddCommand(
+        actionCreateCmd,
+        actionUpdateCmd,
+        actionInvokeCmd,
+        actionGetCmd,
+        actionDeleteCmd,
+        actionListCmd,
+    )
+}
diff --git a/tools/cli/go-whisk-cli/commands/activation.go b/tools/cli/go-whisk-cli/commands/activation.go
new file mode 100644
index 0000000..04c879a
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/activation.go
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+    "os"
+    "os/signal"
+    "syscall"
+    "time"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/fatih/color"
+    "github.com/spf13/cobra"
+)
+
+const (
+    PollInterval = time.Second * 2
+    Delay        = time.Second * 5
+)
+
+// activationCmd represents the activation command
+var activationCmd = &cobra.Command{
+    Use:   "activation",
+    Short: wski18n.T("work with activations"),
+}
+
+var activationListCmd = &cobra.Command{
+    Use:   "list [NAMESPACE or NAME]",
+    Short: wski18n.T("list activations"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        qName := QualifiedName{}
+
+        // Specifying an activation item name filter is optional
+        if len(args) == 1 {
+            whisk.Debug(whisk.DbgInfo, "Activation item name filter '%s' provided\n", args[0])
+            qName, err = parseQualifiedName(args[0])
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+                errStr := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                        map[string]interface{}{"name": args[0], "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            ns := qName.namespace
+            if len(ns) == 0 {
+                whisk.Debug(whisk.DbgError, "Namespace '%s' is invalid\n", ns)
+                errStr := wski18n.T("Namespace '{{.name}}' is invalid", map[string]interface{}{"name": ns})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+
+            client.Namespace = ns
+        } else if whiskErr := checkArgs(args, 0, 1, "Activation list",
+                wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        options := &whisk.ActivationListOptions{
+            Name:  qName.entityName,
+            Limit: flags.common.limit,
+            Skip:  flags.common.skip,
+            Upto:  flags.activation.upto,
+            Since: flags.activation.since,
+            Docs:  flags.common.full,
+        }
+        activations, _, err := client.Activations.List(options)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Activations.List() error: %s\n", err)
+            errStr := wski18n.T("Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": getClientNamespace(), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // When the --full (URL contains "?docs=true") option is specified, display the entire activation details
+        if options.Docs == true {
+            printFullActivationList(activations)
+        } else {
+            printList(activations)
+        }
+
+        return nil
+    },
+}
+
+var activationGetCmd = &cobra.Command{
+    Use:   "get ACTIVATION_ID [FIELD_FILTER]",
+    Short: wski18n.T("get activation"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var field string
+
+        if whiskErr := checkArgs(args, 1, 2, "Activation get",
+                wski18n.T("An activation ID is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if len(args) > 1 {
+            field = args[1]
+
+            if !fieldExists(&whisk.Activation{}, field) {
+                errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+                whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+        }
+
+        id := args[0]
+        activation, _, err := client.Activations.Get(id)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Activations.Get(%s) failed: %s\n", id, err)
+            errStr := wski18n.T("Unable to get activation '{{.id}}': {{.err}}",
+                    map[string]interface{}{"id": id, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        if flags.common.summary {
+            fmt.Printf(
+                wski18n.T("activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n",
+                    map[string]interface{}{
+                        "namespace": activation.Namespace,
+                        "name": activation.Name,
+                        "status": activation.Response.Status,
+                        "time": time.Unix(activation.End/1000, 0)}))
+            printJSON(activation.Response.Result)
+        } else {
+
+            if len(field) > 0 {
+                fmt.Fprintf(color.Output,
+                    wski18n.T("{{.ok}} got activation {{.id}}, displaying field {{.field}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "id": boldString(id),
+                        "field": boldString(field)}))
+                printField(activation, field)
+            } else {
+                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got activation {{.id}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "id": boldString(id)}))
+                printJSON(activation)
+            }
+        }
+
+        return nil
+    },
+}
+
+var activationLogsCmd = &cobra.Command{
+    Use:   "logs ACTIVATION_ID",
+    Short: wski18n.T("get the logs of an activation"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+
+        if whiskErr := checkArgs(args, 1, 1, "Activation logs",
+                wski18n.T("An activation ID is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        id := args[0]
+        activation, _, err := client.Activations.Logs(id)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Activations.Logs(%s) failed: %s\n", id, err)
+            errStr := wski18n.T("Unable to get logs for activation '{{.id}}': {{.err}}",
+                map[string]interface{}{"id": id, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        printActivationLogs(activation.Logs)
+        return nil
+    },
+}
+
+var activationResultCmd = &cobra.Command{
+    Use:   "result ACTIVATION_ID",
+    Short: "get the result of an activation",
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+
+        if whiskErr := checkArgs(args, 1, 1, "Activation result",
+                wski18n.T("An activation ID is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        id := args[0]
+        result, _, err := client.Activations.Result(id)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Activations.result(%s) failed: %s\n", id, err)
+            errStr := wski18n.T("Unable to get result for activation '{{.id}}': {{.err}}",
+                    map[string]interface{}{"id": id, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        printJSON(result.Result)
+        return nil
+    },
+}
+
+var activationPollCmd = &cobra.Command{
+    Use:   "poll [NAMESPACE]",
+    Short: wski18n.T("poll continuously for log messages from currently running actions"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var name string
+        var pollSince int64 // Represents an instant in time (in milliseconds since Jan 1 1970)
+
+        if len(args) == 1 {
+            name = args[0]
+        } else if whiskErr := checkArgs(args, 0, 1, "Activation poll",
+                wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        c := make(chan os.Signal, 1)
+        signal.Notify(c, os.Interrupt)
+        signal.Notify(c, syscall.SIGTERM)
+        go func() {
+            <-c
+            fmt.Println(wski18n.T("Poll terminated"))
+            os.Exit(1)
+        }()
+        fmt.Println(wski18n.T("Enter Ctrl-c to exit."))
+
+        // Map used to track activation records already displayed to the console
+        reported := make(map[string]bool)
+
+        if flags.activation.sinceSeconds+
+        flags.activation.sinceMinutes+
+        flags.activation.sinceHours+
+        flags.activation.sinceDays ==
+        0 {
+            options := &whisk.ActivationListOptions{
+                Limit: 1,
+                Docs:  true,
+            }
+            activationList, _, err := client.Activations.List(options)
+            if err != nil {
+                whisk.Debug(whisk.DbgWarn, "client.Activations.List() error: %s\n", err)
+                whisk.Debug(whisk.DbgWarn, "Ignoring client.Activations.List failure; polling for activations since 'now'\n")
+                pollSince = time.Now().Unix() * 1000    // Convert to milliseconds
+            } else {
+                if len(activationList) > 0 {
+                    lastActivation := activationList[0]     // Activation.Start is in milliseconds since Jan 1 1970
+                    pollSince = lastActivation.Start + 1    // Use it's start time as the basis of the polling
+                }
+            }
+        } else {
+            pollSince = time.Now().Unix() * 1000    // Convert to milliseconds
+
+            // ParseDuration takes a string like "2h45m15s"; create this duration string from the command arguments
+            durationStr := fmt.Sprintf("%dh%dm%ds",
+                flags.activation.sinceHours + flags.activation.sinceDays*24,
+                flags.activation.sinceMinutes,
+                flags.activation.sinceSeconds,
+            )
+            duration, err := time.ParseDuration(durationStr)
+            if err == nil {
+                pollSince = pollSince - duration.Nanoseconds()/1000/1000    // Convert to milliseconds
+            } else {
+                whisk.Debug(whisk.DbgError, "time.ParseDuration(%s) failure: %s\n", durationStr, err)
+            }
+        }
+
+        fmt.Printf(wski18n.T("Polling for activation logs\n"))
+        whisk.Verbose("Polling starts from %s\n", time.Unix(pollSince/1000, 0))
+        localStartTime := time.Now()
+
+        // Polling loop
+        for {
+            if flags.activation.exit > 0 {
+                localDuration := time.Since(localStartTime)
+                if int(localDuration.Seconds()) > flags.activation.exit {
+                    whisk.Debug(whisk.DbgInfo, "Poll time (%d seconds) expired; polling loop stopped\n", flags.activation.exit)
+                    return nil
+                }
+            }
+            whisk.Verbose("Polling for activations since %s\n", time.Unix(pollSince/1000, 0))
+            options := &whisk.ActivationListOptions{
+                Name:  name,
+                Since: pollSince,
+                Docs:  true,
+                Limit: 0,
+                Skip: 0,
+            }
+
+            activations, _, err := client.Activations.List(options)
+            if err != nil {
+                whisk.Debug(whisk.DbgWarn, "client.Activations.List() error: %s\n", err)
+                whisk.Debug(whisk.DbgWarn, "Ignoring client.Activations.List failure; continuing to poll for activations\n")
+                continue
+            }
+
+            for _, activation := range activations {
+                if reported[activation.ActivationID] == true {
+                    continue
+                } else {
+                    fmt.Printf(
+                        wski18n.T("\nActivation: {{.name}} ({{.id}})\n",
+                            map[string]interface{}{"name": activation.Name, "id": activation.ActivationID}))
+                    printJSON(activation.Logs)
+                    reported[activation.ActivationID] = true
+                }
+            }
+            time.Sleep(time.Second * 2)
+        }
+        return nil
+    },
+}
+
+func init() {
+    activationListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of activations from the result"))
+    activationListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of activations from the collection"))
+    activationListCmd.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("include full activation description"))
+    activationListCmd.Flags().Int64Var(&flags.activation.upto, "upto", 0, wski18n.T("return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970"))
+    activationListCmd.Flags().Int64Var(&flags.activation.since, "since", 0, wski18n.T("return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970"))
+
+    activationGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize activation details"))
+
+    activationPollCmd.Flags().IntVarP(&flags.activation.exit, "exit", "e", 0, wski18n.T("stop polling after `SECONDS` seconds"))
+    activationPollCmd.Flags().IntVar(&flags.activation.sinceSeconds, "since-seconds", 0, wski18n.T("start polling for activations `SECONDS` seconds ago"))
+    activationPollCmd.Flags().IntVar(&flags.activation.sinceMinutes, "since-minutes", 0, wski18n.T("start polling for activations `MINUTES` minutes ago"))
+    activationPollCmd.Flags().IntVar(&flags.activation.sinceHours, "since-hours", 0, wski18n.T("start polling for activations `HOURS` hours ago"))
+    activationPollCmd.Flags().IntVar(&flags.activation.sinceDays, "since-days", 0, wski18n.T("start polling for activations `DAYS` days ago"))
+
+    activationCmd.AddCommand(
+        activationListCmd,
+        activationGetCmd,
+        activationLogsCmd,
+        activationResultCmd,
+        activationPollCmd,
+    )
+}
diff --git a/tools/cli/go-whisk-cli/commands/api.go b/tools/cli/go-whisk-cli/commands/api.go
new file mode 100644
index 0000000..a5cbaea
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/api.go
@@ -0,0 +1,1585 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "bytes"
+    "bufio"
+    "errors"
+    "fmt"
+    "reflect"
+    "strconv"
+    "strings"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/fatih/color"
+    "github.com/ghodss/yaml"
+    "github.com/spf13/cobra"
+    "encoding/json"
+)
+
+const (
+    yamlFileExtension = "yaml"
+    ymlFileExtension = "yml"
+
+    formatOptionYaml = "yaml"
+    formatOptionJson = "json"
+)
+
+//////////////
+// Commands //
+//////////////
+
+var apiExperimentalCmd = &cobra.Command{
+    Use:   "api-experimental",
+    Short: wski18n.T("work with APIs (experimental)"),
+}
+
+var apiCmd = &cobra.Command{
+    Use:   "api",
+    Short: wski18n.T("work with APIs"),
+}
+
+var apiCreateCmd = &cobra.Command{
+    Use:           "create ([BASE_PATH] API_PATH API_VERB ACTION] | --config-file CFG_FILE) ",
+    Short:         wski18n.T("create a new API"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+
+        var api *whisk.Api
+        var err error
+
+        if (len(args) == 0 && flags.api.configfile == "") {
+            whisk.Debug(whisk.DbgError, "No swagger file and no arguments\n")
+            errMsg := wski18n.T("Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return whiskErr
+        } else if (len(args) == 0 && flags.api.configfile != "") {
+            api, err = parseSwaggerApi()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err)
+                errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+        } else {
+            if whiskErr := checkArgs(args, 3, 4, "Api create",
+                wski18n.T("Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")); whiskErr != nil {
+                return whiskErr
+            }
+            api, err = parseApi(cmd, args)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseApi(%s, %s) error: %s\n", cmd, args, err)
+                errMsg := wski18n.T("Unable to parse api command arguments: {{.err}}",
+                    map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+        }
+
+        apiCreateReq := new(whisk.ApiCreateRequest)
+        apiCreateReq.ApiDoc = api
+        apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
+        retApi, _, err := client.Apis.Insert(apiCreateReq, apiCreateReqOptions, whisk.DoNotOverwrite)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.Insert(%#v, false) error: %s\n", api, err)
+            errMsg := wski18n.T("Unable to create API: {{.err}}", map[string]interface{}{"err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_NETWORK,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        if (api.Swagger == "") {
+            baseUrl := retApi.BaseUrl
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": strings.TrimSuffix(api.GatewayBasePath, "/")+api.GatewayRelPath,
+                        "verb": api.GatewayMethod,
+                        "name": boldString("/"+api.Action.Namespace+"/"+api.Action.Name),
+                        "fullpath": strings.TrimSuffix(baseUrl, "/")+api.GatewayRelPath,
+                    }))
+        } else {
+            whisk.Debug(whisk.DbgInfo, "Processing swagger based create API response\n")
+            baseUrl := retApi.BaseUrl
+            for path, _ := range retApi.Swagger.Paths {
+                managedUrl := strings.TrimSuffix(baseUrl, "/")+path
+                whisk.Debug(whisk.DbgInfo, "Managed path: %s\n",managedUrl)
+                for op, opv  := range retApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "Path operation: %s\n", op)
+                    fmt.Fprintf(color.Output,
+                        wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+                            map[string]interface{}{
+                                "ok": color.GreenString("ok:"),
+                                "path": path,
+                                "verb": op,
+                                "name": boldString(opv.XOpenWhisk.ActionName),
+                                "fullpath": managedUrl,
+                            }))
+                }
+            }
+        }
+
+
+        return nil
+    },
+}
+
+//var apiUpdateCmd = &cobra.Command{
+//    Use:           "update API_PATH API_VERB ACTION",
+//    Short:         wski18n.T("update an existing API"),
+//    SilenceUsage:  true,
+//    SilenceErrors: true,
+//    PreRunE:       setupClientConfig,
+//    RunE: func(cmd *cobra.Command, args []string) error {
+//
+//        if whiskErr := checkArgs(args, 3, 3, "Api update",
+//            wski18n.T("An API path, an API verb, and an action name are required.")); whiskErr != nil {
+//            return whiskErr
+//        }
+//
+//        api, err := parseApi(cmd, args)
+//        if err != nil {
+//            whisk.Debug(whisk.DbgError, "parseApi(%s, %s) error: %s\n", cmd, args, err)
+//            errMsg := wski18n.T("Unable to parse API command arguments: {{.err}}", map[string]interface{}{"err": err})
+//            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+//                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+//            return whiskErr
+//        }
+//        sendApi := new(whisk.ApiCreateRequest)
+//        sendApi.ApiDoc = api
+//
+//        retApi, _, err := client.Apis.Insert(sendApi, true)
+//        if err != nil {
+//            whisk.Debug(whisk.DbgError, "client.Apis.Insert(%#v, %t, false) error: %s\n", api, err)
+//            errMsg := wski18n.T("Unable to update API: {{.err}}", map[string]interface{}{"err": err})
+//            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_NETWORK,
+//                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+//            return whiskErr
+//        }
+//
+//        fmt.Fprintf(color.Output,
+//            wski18n.T("{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+//                map[string]interface{}{
+//                    "ok": color.GreenString("ok:"),
+//                    "path": api.GatewayRelPath,
+//                    "verb": api.GatewayMethod,
+//                    "name": boldString("/"+api.Action.Name),
+//                    "fullpath": getManagedUrl(retApi, api.GatewayRelPath, api.GatewayMethod),
+//                }))
+//        return nil
+//    },
+//}
+
+var apiGetCmd = &cobra.Command{
+    Use:           "get BASE_PATH | API_NAME",
+    Short:         wski18n.T("get API details"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var isBasePathArg bool = true
+
+        if whiskErr := checkArgs(args, 1, 1, "Api get",
+            wski18n.T("An API base path or API name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        apiGetReq := new(whisk.ApiGetRequest)
+        apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+        apiGetReqOptions.ApiBasePath = args[0]
+        retApi, _, err := client.Apis.Get(apiGetReq, apiGetReqOptions)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+            errMsg := wski18n.T("Unable to get API '{{.name}}': {{.err}}", map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+        whisk.Debug(whisk.DbgInfo, "client.Apis.Get returned: %#v\n", retApi)
+
+        var displayResult interface{} = nil
+        if (flags.common.detail) {
+            if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+                retApi.Apis[0].ApiValue != nil) {
+                displayResult = retApi.Apis[0].ApiValue
+            } else {
+                whisk.Debug(whisk.DbgError, "No result object returned\n")
+            }
+        } else {
+            if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+                retApi.Apis[0].ApiValue != nil &&
+                retApi.Apis[0].ApiValue.Swagger != nil) {
+                  displayResult = retApi.Apis[0].ApiValue.Swagger
+            } else {
+                  whisk.Debug(whisk.DbgError, "No swagger returned\n")
+            }
+        }
+        if (displayResult == nil) {
+            var errMsg string
+            if (isBasePathArg) {
+                errMsg = wski18n.T("API does not exist for basepath {{.basepath}}",
+                    map[string]interface{}{"basepath": args[0]})
+            } else {
+                errMsg = wski18n.T("API does not exist for API name {{.apiname}}",
+                    map[string]interface{}{"apiname": args[0]})
+            }
+
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+        printJSON(displayResult)
+
+        return nil
+    },
+}
+
+var apiDeleteCmd = &cobra.Command{
+    Use:           "delete BASE_PATH | API_NAME [API_PATH [API_VERB]]",
+    Short:         wski18n.T("delete an API"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE:          func(cmd *cobra.Command, args []string) error {
+        if whiskErr := checkArgs(args, 1, 3, "Api delete",
+            wski18n.T("An API base path or API name is required.  An optional API relative path and operation may also be provided.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        apiDeleteReq := new(whisk.ApiDeleteRequest)
+        apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
+        // Is the argument a basepath (must start with /) or an API name
+        if _, ok := isValidBasepath(args[0]); !ok {
+            whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+            apiDeleteReqOptions.ApiBasePath = args[0]
+        } else {
+            apiDeleteReqOptions.ApiBasePath = args[0]
+        }
+
+        if (len(args) > 1) {
+            // Is the API path valid?
+            if whiskErr, ok := isValidRelpath(args[1]); !ok {
+                return whiskErr
+            }
+            apiDeleteReqOptions.ApiRelPath = args[1]
+        }
+        if (len(args) > 2) {
+            // Is the API verb valid?
+            if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+                return whiskErr
+            }
+            apiDeleteReqOptions.ApiVerb = strings.ToUpper(args[2])
+        }
+
+        _, err := client.Apis.Delete(apiDeleteReq, apiDeleteReqOptions)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.Delete(%#v, %#v) error: %s\n", apiDeleteReq, apiDeleteReqOptions, err)
+            errMsg := wski18n.T("Unable to delete API: {{.err}}", map[string]interface{}{"err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        if (len(args) == 1) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted API {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        } else if (len(args) == 2 ) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": apiDeleteReqOptions.ApiRelPath,
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        } else {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": apiDeleteReqOptions.ApiRelPath,
+                        "verb": apiDeleteReqOptions.ApiVerb,
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        }
+
+        return nil
+    },
+}
+
+var fmtString = "%-30s %7s %20s  %s\n"
+var apiListCmd = &cobra.Command{
+    Use:           "list [[BASE_PATH | API_NAME] [API_PATH [API_VERB]]",
+    Short:         wski18n.T("list APIs"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var retApiList *whisk.ApiListResponse
+        var retApi *whisk.ApiGetResponse
+        var retApiArray *whisk.RetApiArray
+
+        if whiskErr := checkArgs(args, 0, 3, "Api list",
+            wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        // Get API request body
+        apiGetReq := new(whisk.ApiGetRequest)
+        apiGetReq.Namespace = client.Config.Namespace
+
+        // Get API request options
+        apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+
+        // List API request query parameters
+        apiListReqOptions := new(whisk.ApiListRequestOptions)
+        apiListReqOptions.Limit = flags.common.limit
+        apiListReqOptions.Skip = flags.common.skip
+
+        if (len(args) == 0) {
+            retApiList, _, err = client.Apis.List(apiListReqOptions)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Apis.List(%#v) error: %s\n", apiListReqOptions, err)
+                errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+            whisk.Debug(whisk.DbgInfo, "client.Apis.List returned: %#v (%+v)\n", retApiList, retApiList)
+            // Cast to a common type to allow for code to print out apilist response or apiget response
+            retApiArray = (*whisk.RetApiArray)(retApiList)
+        } else {
+            // The first argument is either a basepath (must start with /) or an API name
+            apiGetReqOptions.ApiBasePath = args[0]
+            if (len(args) > 1) {
+                // Is the API path valid?
+                if whiskErr, ok := isValidRelpath(args[1]); !ok {
+                    return whiskErr
+                }
+                apiGetReqOptions.ApiRelPath = args[1]
+            }
+            if (len(args) > 2) {
+                // Is the API verb valid?
+                if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+                    return whiskErr
+                }
+                apiGetReqOptions.ApiVerb = strings.ToUpper(args[2])
+            }
+
+            retApi, _, err = client.Apis.Get(apiGetReq, apiGetReqOptions)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+                errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+            whisk.Debug(whisk.DbgInfo, "client.Apis.Get returned: %#v\n", retApi)
+            // Cast to a common type to allow for code to print out apilist response or apiget response
+            retApiArray = (*whisk.RetApiArray)(retApi)
+        }
+
+        // Display the APIs - applying any specified filtering
+        if (flags.common.full) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} APIs\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                    }))
+
+            for i:=0; i<len(retApiArray.Apis); i++ {
+                printFilteredListApi(retApiArray.Apis[i].ApiValue, (*whisk.ApiOptions)(apiGetReqOptions))
+            }
+        } else {
+            // Dynamically create the output format string based on the maximum size of the
+            // fully qualified action name and the API Name.
+            maxActionNameSize := min(40, max(len("Action"), getLargestActionNameSize(retApiArray, (*whisk.ApiOptions)(apiGetReqOptions))))
+            maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSize(retApiArray, (*whisk.ApiOptions)(apiGetReqOptions))))
+            fmtString = "%-"+strconv.Itoa(maxActionNameSize)+"s %7s %"+strconv.Itoa(maxApiNameSize+1)+"s  %s\n"
+
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} APIs\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                    }))
+            fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+
+            for i:=0; i<len(retApiArray.Apis); i++ {
+                printFilteredListRow(retApiArray.Apis[i].ApiValue, (*whisk.ApiOptions)(apiGetReqOptions), maxActionNameSize, maxApiNameSize)
+            }
+        }
+
+        return nil
+    },
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration.  For each API endpoint matching the filtering criteria, display
+ * each endpoint's configuration - one line per configuration property (action name, verb, api name, api gw url)
+ */
+func printFilteredListApi(resultApi *whisk.RetApi, api *whisk.ApiOptions) {
+    baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+    apiName := resultApi.Swagger.Info.Title
+    basePath := resultApi.Swagger.BasePath
+    if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+        for path, _ := range resultApi.Swagger.Paths {
+            whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing api relpath: %s\n", path)
+            if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+                whisk.Debug(whisk.DbgInfo, "apiGetCmd: relpath matches\n")
+                for op, opv  := range resultApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing operation: '%s'\n", op)
+                    if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+                        whisk.Debug(whisk.DbgInfo, "apiGetCmd: operation matches: %#v\n", opv)
+                        var actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                        fmt.Printf("%s: %s\n", wski18n.T("Action"), actionName)
+                        fmt.Printf("  %s: %s\n", wski18n.T("API Name"), apiName)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Base path"), basePath)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Path"), path)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Verb"), op)
+                        fmt.Printf("  %s: %s\n", wski18n.T("URL"), baseUrl+path)
+                    }
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration.  For each API matching the filtering criteria, display the API
+ * on a single line (action name, verb, api name, api gw url).
+ *
+ * NOTE: Large action name and api name value will be truncated by their associated max size parameters.
+ */
+func printFilteredListRow(resultApi *whisk.RetApi, api *whisk.ApiOptions, maxActionNameSize int, maxApiNameSize int) {
+    baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+    apiName := resultApi.Swagger.Info.Title
+    if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+        for path, _ := range resultApi.Swagger.Paths {
+            whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing api relpath: %s\n", path)
+            if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+                whisk.Debug(whisk.DbgInfo, "apiGetCmd: relpath matches\n")
+                for op, opv  := range resultApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "apiGetCmd: comparing operation: '%s'\n", op)
+                    if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+                        whisk.Debug(whisk.DbgInfo, "apiGetCmd: operation matches: %#v\n", opv)
+                        var actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                        fmt.Printf(fmtString,
+                            actionName[0 : min(len(actionName), maxActionNameSize)],
+                            op,
+                            apiName[0 : min(len(apiName), maxApiNameSize)],
+                            baseUrl+path)
+                    }
+                }
+            }
+        }
+    }
+}
+
+func getLargestActionNameSize(retApiArray *whisk.RetApiArray, api *whisk.ApiOptions) int {
+    var maxNameSize = 0
+    for i:=0; i<len(retApiArray.Apis); i++ {
+        var resultApi = retApiArray.Apis[i].ApiValue
+        if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+            for path, _ := range resultApi.Swagger.Paths {
+                whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+                if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+                    whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+                    for op, opv  := range resultApi.Swagger.Paths[path] {
+                        whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+                        if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+                            whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+                            var fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                            if (len(fullActionName) > maxNameSize) {
+                                maxNameSize = len(fullActionName)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return maxNameSize
+}
+
+func getLargestApiNameSize(retApiArray *whisk.RetApiArray, api *whisk.ApiOptions) int {
+    var maxNameSize = 0
+    for i:=0; i<len(retApiArray.Apis); i++ {
+        var resultApi = retApiArray.Apis[i].ApiValue
+        apiName := resultApi.Swagger.Info.Title
+        if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+            for path, _ := range resultApi.Swagger.Paths {
+                whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+                if ( len(api.ApiRelPath) == 0 || path == api.ApiRelPath) {
+                    whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+                    for op, opv  := range resultApi.Swagger.Paths[path] {
+                        whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+                        if ( len(api.ApiVerb) == 0 || strings.ToLower(op) == strings.ToLower(api.ApiVerb)) {
+                            whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+                            if (len(apiName) > maxNameSize) {
+                                maxNameSize = len(apiName)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return maxNameSize
+}
+
+/*
+ * if # args = 4
+ * args[0] = API base path
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
+ *
+ * if # args = 3
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
+ */
+func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, error) {
+    var err error
+    var basepath string = "/"
+    var apiname string
+    var basepathArgIsApiName = false;
+
+    api := new(whisk.Api)
+
+    if (len(args) > 3) {
+        // Is the argument a basepath (must start with /) or an API name
+        if _, ok := isValidBasepath(args[0]); !ok {
+            whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+            basepathArgIsApiName = true;
+        }
+        basepath = args[0]
+
+        // Shift the args so the remaining code works with or without the explicit base path arg
+        args = args[1:]
+    }
+
+    // Is the API path valid?
+    if (len(args) > 0) {
+        if whiskErr, ok := isValidRelpath(args[0]); !ok {
+            return nil, whiskErr
+        }
+        api.GatewayRelPath = args[0]    // Maintain case as URLs may be case-sensitive
+    }
+
+    // Is the API verb valid?
+    if (len(args) > 1) {
+        if whiskErr, ok := IsValidApiVerb(args[1]); !ok {
+            return nil, whiskErr
+        }
+        api.GatewayMethod = strings.ToUpper(args[1])
+    }
+
+    // Is the specified action name valid?
+    var qName QualifiedName
+    if (len(args) == 3) {
+        qName = QualifiedName{}
+        qName, err = parseQualifiedName(args[2])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[2], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid action name: {{.err}}",
+                map[string]interface{}{"name": args[2], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, whiskErr
+        }
+        if (qName.entityName == "") {
+            whisk.Debug(whisk.DbgError, "Action name '%s' is invalid\n", args[2])
+            errMsg := wski18n.T("'{{.name}}' is not a valid action name.", map[string]interface{}{"name": args[2]})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, whiskErr
+        }
+    }
+
+    if ( len(flags.api.apiname) > 0 ) {
+        if (basepathArgIsApiName) {
+            // Specifying API name as argument AND as a --apiname option value is invalid
+            whisk.Debug(whisk.DbgError, "API is specified as an argument '%s' and as a flag '%s'\n", basepath, flags.api.apiname)
+            errMsg := wski18n.T("An API name can only be specified once.")
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, whiskErr
+        }
+        apiname = flags.api.apiname
+    }
+
+    api.Namespace = client.Config.Namespace
+    api.Action = new(whisk.ApiAction)
+    api.Action.BackendUrl = "https://" + client.Config.Host + "/api/v1/namespaces/" + qName.namespace + "/actions/" + qName.entityName
+    api.Action.BackendMethod = "POST"
+    api.Action.Name = qName.entityName
+    api.Action.Namespace = qName.namespace
+    api.Action.Auth = client.Config.AuthToken
+    api.ApiName = apiname
+    api.GatewayBasePath = basepath
+    if (!basepathArgIsApiName) { api.Id = "API:"+api.Namespace+":"+api.GatewayBasePath }
+
+    whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
+    return api, nil
+}
+
+func parseSwaggerApi() (*whisk.Api, error) {
+    // Test is for completeness, but this situation should only arise due to an internal error
+    if ( len(flags.api.configfile) == 0 ) {
+        whisk.Debug(whisk.DbgError, "No swagger file is specified\n")
+        errMsg := wski18n.T("A configuration file was not specified.")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg),whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    swagger, err:= readFile(flags.api.configfile)
+    if ( err != nil ) {
+        whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": flags.api.configfile, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    // Parse the JSON into a swagger object
+    swaggerObj := new(whisk.ApiSwagger)
+    err = json.Unmarshal([]byte(swagger), swaggerObj)
+    if ( err != nil ) {
+        whisk.Debug(whisk.DbgError, "JSON parse of `%s' error: %s\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": flags.api.configfile, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    if (swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil) {
+        whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    if _, ok := isValidBasepath(swaggerObj.BasePath); !ok {
+        whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    api := new(whisk.Api)
+    api.Namespace = client.Config.Namespace
+    api.Swagger = swagger
+
+    return api, nil
+}
+
+func IsValidApiVerb(verb string) (error, bool) {
+    // Is the API verb valid?
+    if _, ok := whisk.ApiVerbs[strings.ToUpper(verb)]; !ok {
+        whisk.Debug(whisk.DbgError, "Invalid API verb: %s\n", verb)
+        errMsg := wski18n.T("'{{.verb}}' is not a valid API verb.  Valid values are: {{.verbs}}",
+                map[string]interface{}{
+                    "verb": verb,
+                    "verbs": reflect.ValueOf(whisk.ApiVerbs).MapKeys()})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr, false
+    }
+    return nil, true
+}
+
+func hasPathPrefix(path string) (error, bool) {
+    if (! strings.HasPrefix(path, "/")) {
+        whisk.Debug(whisk.DbgError, "path does not begin with '/': %s\n", path)
+        errMsg := wski18n.T("'{{.path}}' must begin with '/'.",
+                map[string]interface{}{
+                    "path": path,
+                })
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr, false
+    }
+    return nil, true
+}
+
+func isValidBasepath(basepath string) (error, bool) {
+    if whiskerr, ok := hasPathPrefix(basepath); !ok {
+        return whiskerr, false
+    }
+    return nil, true
+}
+
+func isValidRelpath(relpath string) (error, bool) {
+    if whiskerr, ok := hasPathPrefix(relpath); !ok {
+        return whiskerr, false
+    }
+    return nil, true
+}
+
+
+/*
+ * Pull the managedUrl (external API URL) from the API configuration
+ */
+func getManagedUrl(api *whisk.RetApi, relpath string, operation string) (url string) {
+    baseUrl := strings.TrimSuffix(api.BaseUrl, "/")
+    whisk.Debug(whisk.DbgInfo, "getManagedUrl: baseUrl = %s, relpath = %s, operation = %s\n", baseUrl, relpath, operation)
+    for path, _ := range api.Swagger.Paths {
+        whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing api relpath: %s\n", path)
+        if (path == relpath) {
+            whisk.Debug(whisk.DbgInfo, "getManagedUrl: relpath matches '%s'\n", relpath)
+            for op, _  := range api.Swagger.Paths[path] {
+                whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing operation: '%s'\n", op)
+                if (strings.ToLower(op) == strings.ToLower(operation)) {
+                    whisk.Debug(whisk.DbgInfo, "getManagedUrl: operation matches: %s\n", operation)
+                    url = baseUrl+path
+                }
+            }
+        }
+    }
+    return url
+}
+
+/////////////
+// V2 Cmds //
+/////////////
+var apiCreateCmdV2 = &cobra.Command{
+    Use:           "create ([BASE_PATH] API_PATH API_VERB ACTION] | --config-file CFG_FILE) ",
+    Short:         wski18n.T("create a new API"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var api *whisk.Api
+        var err error
+        var qname *QualifiedName
+
+        if (len(args) == 0 && flags.api.configfile == "") {
+            whisk.Debug(whisk.DbgError, "No swagger file and no arguments\n")
+            errMsg := wski18n.T("Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return whiskErr
+        } else if (len(args) == 0 && flags.api.configfile != "") {
+            api, err = parseSwaggerApiV2()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err)
+                errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+        } else {
+            if whiskErr := checkArgs(args, 3, 4, "Api create",
+                wski18n.T("Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")); whiskErr != nil {
+                return whiskErr
+            }
+            api, qname, err = parseApiV2(cmd, args)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseApiV2(%s, %s) error: %s\n", cmd, args, err)
+                errMsg := wski18n.T("Unable to parse api command arguments: {{.err}}",
+                    map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+
+            // Confirm that the specified action is a web-action
+            err = isWebAction(client, *qname)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "isWebAction(%v) is false: %s\n", qname, err)
+                whiskErr := whisk.MakeWskError(err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+        }
+
+        apiCreateReq := new(whisk.ApiCreateRequest)
+        apiCreateReq.ApiDoc = api
+
+        apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
+        if apiCreateReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+            return err
+        }
+        if apiCreateReqOptions.AccessToken, err = getAccessToken(); err != nil {
+            return err
+        }
+        apiCreateReqOptions.ResponseType = flags.api.resptype
+        whisk.Debug(whisk.DbgInfo, "AccessToken: %s\nSpaceGuid: %s\nResponsType: %s",
+            apiCreateReqOptions.AccessToken, apiCreateReqOptions.SpaceGuid, apiCreateReqOptions.ResponseType)
+
+        retApi, _, err := client.Apis.InsertV2(apiCreateReq, apiCreateReqOptions, whisk.DoNotOverwrite)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.InsertV2(%#v, false) error: %s\n", api, err)
+            errMsg := wski18n.T("Unable to create API: {{.err}}", map[string]interface{}{"err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        if (api.Swagger == "") {
+            baseUrl := retApi.BaseUrl
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": strings.TrimSuffix(api.GatewayBasePath, "/")+api.GatewayRelPath,
+                        "verb": api.GatewayMethod,
+                        "name": boldString("/"+api.Action.Namespace+"/"+api.Action.Name),
+                        "fullpath": strings.TrimSuffix(baseUrl, "/")+api.GatewayRelPath,
+                    }))
+        } else {
+            whisk.Debug(whisk.DbgInfo, "Processing swagger based create API response\n")
+            baseUrl := retApi.BaseUrl
+            for path, _ := range retApi.Swagger.Paths {
+                managedUrl := strings.TrimSuffix(baseUrl, "/")+path
+                whisk.Debug(whisk.DbgInfo, "Managed path: %s\n",managedUrl)
+                for op, opv  := range retApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "Path operation: %s\n", op)
+                    var fqActionName string
+                    if (len(opv.XOpenWhisk.Package) > 0) {
+                        fqActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+                    } else {
+                        fqActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                    }
+                    whisk.Debug(whisk.DbgInfo, "baseUrl %s  Path %s  Path obj %+v\n", baseUrl, path, opv)
+                    fmt.Fprintf(color.Output,
+                        wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+                            map[string]interface{}{
+                                "ok": color.GreenString("ok:"),
+                                "path": strings.TrimSuffix(retApi.Swagger.BasePath, "/") + path,
+                                "verb": op,
+                                "name": boldString(fqActionName),
+                                "fullpath": managedUrl,
+                            }))
+                }
+            }
+        }
+
+
+        return nil
+    },
+}
+
+var apiGetCmdV2 = &cobra.Command{
+    Use:           "get BASE_PATH | API_NAME",
+    Short:         wski18n.T("get API details"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var isBasePathArg bool = true
+
+        if whiskErr := checkArgs(args, 1, 1, "Api get",
+            wski18n.T("An API base path or API name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if ( cmd.LocalFlags().Changed("format") &&
+             strings.ToLower(flags.common.format) != formatOptionYaml &&
+             strings.ToLower(flags.common.format) != formatOptionJson) {
+            errMsg := wski18n.T("Invalid format type: {{.type}}", map[string]interface{}{"type": flags.common.format})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        apiGetReq := new(whisk.ApiGetRequest)
+        apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+        apiGetReqOptions.ApiBasePath = args[0]
+        if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+            return err
+        }
+        if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
+            return err
+        }
+
+        retApi, _, err := client.Apis.GetV2(apiGetReq, apiGetReqOptions)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.GetV2(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+            errMsg := wski18n.T("Unable to get API '{{.name}}': {{.err}}", map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+        whisk.Debug(whisk.DbgInfo, "client.Apis.GetV2 returned: %#v\n", retApi)
+
+        var displayResult interface{} = nil
+        if (flags.common.detail) {
+            if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+            retApi.Apis[0].ApiValue != nil) {
+                displayResult = retApi.Apis[0].ApiValue
+            } else {
+                whisk.Debug(whisk.DbgError, "No result object returned\n")
+            }
+        } else {
+            if (retApi.Apis != nil && len(retApi.Apis) > 0 &&
+            retApi.Apis[0].ApiValue != nil &&
+            retApi.Apis[0].ApiValue.Swagger != nil) {
+                displayResult = retApi.Apis[0].ApiValue.Swagger
+            } else {
+                whisk.Debug(whisk.DbgError, "No swagger returned\n")
+            }
+        }
+        if (displayResult == nil) {
+            var errMsg string
+            if (isBasePathArg) {
+                errMsg = wski18n.T("API does not exist for basepath {{.basepath}}",
+                    map[string]interface{}{"basepath": args[0]})
+            } else {
+                errMsg = wski18n.T("API does not exist for API name {{.apiname}}",
+                    map[string]interface{}{"apiname": args[0]})
+            }
+
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        if ( cmd.LocalFlags().Changed("format") && strings.ToLower(flags.common.format) == formatOptionYaml ) {
+            var jsonOutputBuffer bytes.Buffer
+            var jsonOutputWriter = bufio.NewWriter(&jsonOutputBuffer)
+            printJSON(displayResult, jsonOutputWriter)
+            jsonOutputWriter.Flush()
+            yamlbytes, err := yaml.JSONToYAML(jsonOutputBuffer.Bytes())
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "yaml.JSONToYAML() error: %s\n", err)
+                errMsg := wski18n.T("Unable to convert API into YAML: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+            fmt.Println(string(yamlbytes))
+        } else {
+            printJSON(displayResult)
+        }
+
+        return nil
+    },
+}
+
+var apiDeleteCmdV2 = &cobra.Command{
+    Use:           "delete BASE_PATH | API_NAME [API_PATH [API_VERB]]",
+    Short:         wski18n.T("delete an API"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 3, "Api delete",
+            wski18n.T("An API base path or API name is required.  An optional API relative path and operation may also be provided.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        apiDeleteReq := new(whisk.ApiDeleteRequest)
+        apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
+        if apiDeleteReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+            return err
+        }
+        if apiDeleteReqOptions.AccessToken, err = getAccessToken(); err != nil {
+            return err
+        }
+
+        // Is the argument a basepath (must start with /) or an API name
+        if _, ok := isValidBasepath(args[0]); !ok {
+            whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+            apiDeleteReqOptions.ApiBasePath = args[0]
+        } else {
+            apiDeleteReqOptions.ApiBasePath = args[0]
+        }
+
+        if (len(args) > 1) {
+            // Is the API path valid?
+            if whiskErr, ok := isValidRelpath(args[1]); !ok {
+                return whiskErr
+            }
+            apiDeleteReqOptions.ApiRelPath = args[1]
+        }
+        if (len(args) > 2) {
+            // Is the API verb valid?
+            if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+                return whiskErr
+            }
+            apiDeleteReqOptions.ApiVerb = strings.ToUpper(args[2])
+        }
+
+        _, err = client.Apis.DeleteV2(apiDeleteReq, apiDeleteReqOptions)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Apis.DeleteV2(%#v, %#v) error: %s\n", apiDeleteReq, apiDeleteReqOptions, err)
+            errMsg := wski18n.T("Unable to delete API: {{.err}}", map[string]interface{}{"err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        if (len(args) == 1) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted API {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        } else if (len(args) == 2 ) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": apiDeleteReqOptions.ApiRelPath,
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        } else {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                        "path": apiDeleteReqOptions.ApiRelPath,
+                        "verb": apiDeleteReqOptions.ApiVerb,
+                        "basepath": apiDeleteReqOptions.ApiBasePath,
+                    }))
+        }
+
+        return nil
+    },
+}
+
+var apiListCmdV2 = &cobra.Command{
+    Use:           "list [[BASE_PATH | API_NAME] [API_PATH [API_VERB]]",
+    Short:         wski18n.T("list APIs"),
+    SilenceUsage:  true,
+    SilenceErrors: true,
+    PreRunE:       setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var retApiList *whisk.ApiListResponseV2
+        var retApi *whisk.ApiGetResponseV2
+        var retApiArray *whisk.RetApiArrayV2
+        var apiPath string
+        var apiVerb string
+
+        if whiskErr := checkArgs(args, 0, 3, "Api list",
+            wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if (len(args) == 0) {
+            // List API request query parameters
+            apiListReqOptions := new(whisk.ApiListRequestOptions)
+            apiListReqOptions.Limit = flags.common.limit
+            apiListReqOptions.Skip = flags.common.skip
+            if apiListReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+                return err
+            }
+            if apiListReqOptions.AccessToken, err = getAccessToken(); err != nil {
+                return err
+            }
+
+            retApiList, _, err = client.Apis.ListV2(apiListReqOptions)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Apis.ListV2(%#v) error: %s\n", apiListReqOptions, err)
+                errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+            whisk.Debug(whisk.DbgInfo, "client.Apis.ListV2 returned: %#v (%+v)\n", retApiList, retApiList)
+            // Cast to a common type to allow for code to print out apilist response or apiget response
+            retApiArray = (*whisk.RetApiArrayV2)(retApiList)
+        } else {
+            // Get API request body
+            apiGetReq := new(whisk.ApiGetRequest)
+            apiGetReq.Namespace = client.Config.Namespace
+            // Get API request options
+            apiGetReqOptions := new(whisk.ApiGetRequestOptions)
+            if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
+                return err
+            }
+            if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
+                return err
+            }
+
+            // The first argument is either a basepath (must start with /) or an API name
+            apiGetReqOptions.ApiBasePath = args[0]
+            if (len(args) > 1) {
+                // Is the API path valid?
+                if whiskErr, ok := isValidRelpath(args[1]); !ok {
+                    return whiskErr
+                }
+                apiPath = args[1]
+                apiGetReqOptions.ApiRelPath = apiPath
+            }
+            if (len(args) > 2) {
+                // Is the API verb valid?
+                if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
+                    return whiskErr
+                }
+                apiVerb = strings.ToUpper(args[2])
+                apiGetReqOptions.ApiVerb = apiVerb
+            }
+
+            retApi, _, err = client.Apis.GetV2(apiGetReq, apiGetReqOptions)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Apis.GetV2(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
+                errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+            whisk.Debug(whisk.DbgInfo, "client.Apis.GetV2 returned: %#v\n", retApi)
+            // Cast to a common type to allow for code to print out apilist response or apiget response
+            retApiArray = (*whisk.RetApiArrayV2)(retApi)
+        }
+
+        // Display the APIs - applying any specified filtering
+        if (flags.common.full) {
+            fmt.Fprintf(color.Output,
+                wski18n.T("{{.ok}} APIs\n",
+                    map[string]interface{}{
+                        "ok": color.GreenString("ok:"),
+                    }))
+
+            for i:=0; i<len(retApiArray.Apis); i++ {
+                printFilteredListApiV2(retApiArray.Apis[i].ApiValue, apiPath, apiVerb)
+            }
+        } else {
+            if (len(retApiArray.Apis) > 0) {
+                // Dynamically create the output format string based on the maximum size of the
+                // fully qualified action name and the API Name.
+                maxActionNameSize := min(40, max(len("Action"), getLargestActionNameSizeV2(retApiArray, apiPath, apiVerb)))
+                maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSizeV2(retApiArray, apiPath, apiVerb)))
+                fmtString = "%-"+strconv.Itoa(maxActionNameSize)+"s %7s %"+strconv.Itoa(maxApiNameSize+1)+"s  %s\n"
+                fmt.Fprintf(color.Output,
+                    wski18n.T("{{.ok}} APIs\n",
+                        map[string]interface{}{
+                            "ok": color.GreenString("ok:"),
+                        }))
+                fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+                for i:=0; i<len(retApiArray.Apis); i++ {
+                    printFilteredListRowV2(retApiArray.Apis[i].ApiValue, apiPath, apiVerb, maxActionNameSize, maxApiNameSize)
+                }
+            } else {
+                fmt.Fprintf(color.Output,
+                    wski18n.T("{{.ok}} APIs\n",
+                        map[string]interface{}{
+                            "ok": color.GreenString("ok:"),
+                        }))
+                fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
+            }
+        }
+
+        return nil
+    },
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration.  For each API endpoint matching the filtering criteria, display
+ * each endpoint's configuration - one line per configuration property (action name, verb, api name, api gw url)
+ */
+func printFilteredListApiV2(resultApi *whisk.RetApiV2, apiPath string, apiVerb string) {
+    baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+    apiName := resultApi.Swagger.Info.Title
+    basePath := resultApi.Swagger.BasePath
+    if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+        for path, _ := range resultApi.Swagger.Paths {
+            whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: comparing api relpath: %s\n", path)
+            if ( len(apiPath) == 0 || path == apiPath) {
+                whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: relpath matches\n")
+                for op, opv  := range resultApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: comparing operation: '%s'\n", op)
+                    if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+                        whisk.Debug(whisk.DbgInfo, "printFilteredListApiV2: operation matches: %#v\n", opv)
+                        var actionName string
+                        if (len(opv.XOpenWhisk.Package) > 0) {
+                            actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+                        } else {
+                            actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                        }
+                        fmt.Printf("%s: %s\n", wski18n.T("Action"), actionName)
+                        fmt.Printf("  %s: %s\n", wski18n.T("API Name"), apiName)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Base path"), basePath)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Path"), path)
+                        fmt.Printf("  %s: %s\n", wski18n.T("Verb"), op)
+                        fmt.Printf("  %s: %s\n", wski18n.T("URL"), baseUrl+path)
+                    }
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Takes an API object (containing one more more single basepath/relpath/operation triplets)
+ * and some filtering configuration.  For each API matching the filtering criteria, display the API
+ * on a single line (action name, verb, api name, api gw url).
+ *
+ * NOTE: Large action name and api name value will be truncated by their associated max size parameters.
+ */
+func printFilteredListRowV2(resultApi *whisk.RetApiV2, apiPath string, apiVerb string, maxActionNameSize int, maxApiNameSize int) {
+    baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
+    apiName := resultApi.Swagger.Info.Title
+    if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+        for path, _ := range resultApi.Swagger.Paths {
+            whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: comparing api relpath: %s\n", path)
+            if ( len(apiPath) == 0 || path == apiPath) {
+                whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: relpath matches\n")
+                for op, opv  := range resultApi.Swagger.Paths[path] {
+                    whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: comparing operation: '%s'\n", op)
+                    if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+                        whisk.Debug(whisk.DbgInfo, "printFilteredListRowV2: operation matches: %#v\n", opv)
+                        var actionName string
+                        if (len(opv.XOpenWhisk.Package) > 0) {
+                            actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+                        } else {
+                            actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                        }
+                        fmt.Printf(fmtString,
+                            actionName[0 : min(len(actionName), maxActionNameSize)],
+                            op,
+                            apiName[0 : min(len(apiName), maxApiNameSize)],
+                            baseUrl+path)
+                    }
+                }
+            }
+        }
+    }
+}
+
+func getLargestActionNameSizeV2(retApiArray *whisk.RetApiArrayV2, apiPath string, apiVerb string) int {
+    var maxNameSize = 0
+    for i:=0; i<len(retApiArray.Apis); i++ {
+        var resultApi = retApiArray.Apis[i].ApiValue
+        if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+            for path, _ := range resultApi.Swagger.Paths {
+                whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+                if ( len(apiPath) == 0 || path == apiPath) {
+                    whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+                    for op, opv  := range resultApi.Swagger.Paths[path] {
+                        whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+                        if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+                            whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+                            var fullActionName string
+                            if (len(opv.XOpenWhisk.Package) > 0) {
+                                fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.Package+"/"+opv.XOpenWhisk.ActionName
+                            } else {
+                                fullActionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
+                            }
+                            if (len(fullActionName) > maxNameSize) {
+                                maxNameSize = len(fullActionName)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return maxNameSize
+}
+
+func getLargestApiNameSizeV2(retApiArray *whisk.RetApiArrayV2, apiPath string, apiVerb string) int {
+    var maxNameSize = 0
+    for i:=0; i<len(retApiArray.Apis); i++ {
+        var resultApi = retApiArray.Apis[i].ApiValue
+        apiName := resultApi.Swagger.Info.Title
+        if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
+            for path, _ := range resultApi.Swagger.Paths {
+                whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: %s\n", path)
+                if ( len(apiPath) == 0 || path == apiPath) {
+                    whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
+                    for op, opv  := range resultApi.Swagger.Paths[path] {
+                        whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
+                        if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
+                            whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
+                            if (len(apiName) > maxNameSize) {
+                                maxNameSize = len(apiName)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return maxNameSize
+}
+
+/*
+ * if # args = 4
+ * args[0] = API base path
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
+ *
+ * if # args = 3
+ * args[0] = API relative path
+ * args[1] = API verb
+ * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
+ */
+func parseApiV2(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, error) {
+    var err error
+    var basepath string = "/"
+    var apiname string
+    var basepathArgIsApiName = false;
+
+    api := new(whisk.Api)
+
+    if (len(args) > 3) {
+        // Is the argument a basepath (must start with /) or an API name
+        if _, ok := isValidBasepath(args[0]); !ok {
+            whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
+            basepathArgIsApiName = true;
+        }
+        basepath = args[0]
+
+        // Shift the args so the remaining code works with or without the explicit base path arg
+        args = args[1:]
+    }
+
+    // Is the API path valid?
+    if (len(args) > 0) {
+        if whiskErr, ok := isValidRelpath(args[0]); !ok {
+            return nil, nil, whiskErr
+        }
+        api.GatewayRelPath = args[0]    // Maintain case as URLs may be case-sensitive
+    }
+
+    // Is the API verb valid?
+    if (len(args) > 1) {
+        if whiskErr, ok := IsValidApiVerb(args[1]); !ok {
+            return nil, nil, whiskErr
+        }
+        api.GatewayMethod = strings.ToUpper(args[1])
+    }
+
+    // Is the specified action name valid?
+    var qName QualifiedName
+    if (len(args) == 3) {
+        qName = QualifiedName{}
+        qName, err = parseQualifiedName(args[2])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[2], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid action name: {{.err}}",
+                map[string]interface{}{"name": args[2], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, nil, whiskErr
+        }
+        if (qName.entityName == "") {
+            whisk.Debug(whisk.DbgError, "Action name '%s' is invalid\n", args[2])
+            errMsg := wski18n.T("'{{.name}}' is not a valid action name.", map[string]interface{}{"name": args[2]})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, nil, whiskErr
+        }
+    }
+
+    if ( len(flags.api.apiname) > 0 ) {
+        if (basepathArgIsApiName) {
+            // Specifying API name as argument AND as a --apiname option value is invalid
+            whisk.Debug(whisk.DbgError, "API is specified as an argument '%s' and as a flag '%s'\n", basepath, flags.api.apiname)
+            errMsg := wski18n.T("An API name can only be specified once.")
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return nil, nil, whiskErr
+        }
+        apiname = flags.api.apiname
+    }
+
+    api.Namespace = client.Config.Namespace
+    api.Action = new(whisk.ApiAction)
+    var urlActionPackage string
+    if (len(qName.packageName) > 0) {
+        urlActionPackage = qName.packageName
+    } else {
+        urlActionPackage = "default"
+    }
+    api.Action.BackendUrl = "https://" + client.Config.Host + "/api/v1/web/" + qName.namespace + "/" + urlActionPackage + "/" + qName.entity + ".http"
+    api.Action.BackendMethod = api.GatewayMethod
+    api.Action.Name = qName.entityName
+    api.Action.Namespace = qName.namespace
+    api.Action.Auth = client.Config.AuthToken
+    api.ApiName = apiname
+    api.GatewayBasePath = basepath
+    if (!basepathArgIsApiName) { api.Id = "API:"+api.Namespace+":"+api.GatewayBasePath }
+
+    whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
+    return api, &qName, nil
+}
+
+func parseSwaggerApiV2() (*whisk.Api, error) {
+    // Test is for completeness, but this situation should only arise due to an internal error
+    if ( len(flags.api.configfile) == 0 ) {
+        whisk.Debug(whisk.DbgError, "No swagger file is specified\n")
+        errMsg := wski18n.T("A configuration file was not specified.")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg),whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    swagger, err:= readFile(flags.api.configfile)
+    if ( err != nil ) {
+        whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}",
+            map[string]interface{}{"name": flags.api.configfile, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    // Check if this swagger is in JSON or YAML format
+    isYaml := strings.HasSuffix(flags.api.configfile, yamlFileExtension) || strings.HasSuffix(flags.api.configfile, ymlFileExtension)
+    if isYaml {
+        whisk.Debug(whisk.DbgInfo, "Converting YAML formated API configuration into JSON\n")
+        jsonbytes, err := yaml.YAMLToJSON([]byte(swagger))
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "yaml.YAMLToJSON() error: %s\n", err)
+            errMsg := wski18n.T("Unable to parse YAML configuration file: {{.err}}", map[string]interface{}{"err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return nil, whiskErr
+        }
+        swagger = string(jsonbytes)
+    }
+
+    // Parse the JSON into a swagger object
+    swaggerObj := new(whisk.ApiSwaggerV2)
+    err = json.Unmarshal([]byte(swagger), swaggerObj)
+    if ( err != nil ) {
+        whisk.Debug(whisk.DbgError, "JSON parse of `%s' error: %s\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}",
+            map[string]interface{}{"name": flags.api.configfile, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    if (swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil) {
+        whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    if _, ok := isValidBasepath(swaggerObj.BasePath); !ok {
+        whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n", flags.api.configfile, err)
+        errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    api := new(whisk.Api)
+    api.Namespace = client.Config.Namespace
+    api.Swagger = swagger
+
+    return api, nil
+}
+
+func getAccessToken() (string, error) {
+    var token string = "DUMMY TOKEN"
+    var err error
+
+    props, err := readProps(Properties.PropsFile)
+    if err == nil {
+        if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
+            token = props["APIGW_ACCESS_TOKEN"]
+        }
+    } else {
+        whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+        errStr := wski18n.T("Unable to obtain the API Gateway access token from the properties file: {{.err}}", map[string]interface{}{"err": err})
+        err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+    }
+
+    return token, err
+}
+
+func getUserContextId() (string, error) {
+    var guid string
+    var err error
+
+    props, err := readProps(Properties.PropsFile)
+    if err == nil {
+        if len(props["AUTH"]) > 0 {
+            guid = strings.Split(props["AUTH"], ":")[0]
+        } else {
+            whisk.Debug(whisk.DbgError, "AUTH property not set in properties file: %s\n", Properties.PropsFile)
+            errStr := wski18n.T("Authorization key is not configured (--auth is required)")
+            err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        }
+    } else {
+        whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+        errStr := wski18n.T("Unable to obtain the auth key from the properties file: {{.err}}", map[string]interface{}{"err": err})
+        err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+    }
+
+    return guid, err
+}
+
+///////////
+// Flags //
+///////////
+
+func init() {
+    apiCreateCmd.Flags().StringVarP(&flags.api.apiname, "apiname", "n", "", wski18n.T("Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)"))
+    apiCreateCmd.Flags().StringVarP(&flags.api.configfile, "config-file", "c", "", wski18n.T("`CFG_FILE` containing API configuration in swagger JSON format"))
+    //apiUpdateCmd.Flags().StringVarP(&flags.api.action, "action", "a", "", wski18n.T("`ACTION` to invoke when API is called"))
+    //apiUpdateCmd.Flags().StringVarP(&flags.api.path, "path", "p", "", wski18n.T("relative `PATH` of API"))
+    //apiUpdateCmd.Flags().StringVarP(&flags.api.verb, "method", "m", "", wski18n.T("API `VERB`"))
+    apiGetCmd.Flags().BoolVarP(&flags.common.detail, "full", "f", false, wski18n.T("display full API configuration details"))
+    apiListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+    apiListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+    apiListCmd.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
+    apiExperimentalCmd.AddCommand(
+        apiCreateCmd,
+        //apiUpdateCmd,
+        apiGetCmd,
+        apiDeleteCmd,
+        apiListCmd,
+    )
+
+    apiCreateCmdV2.Flags().StringVarP(&flags.api.apiname, "apiname", "n", "", wski18n.T("Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)"))
+    apiCreateCmdV2.Flags().StringVarP(&flags.api.configfile, "config-file", "c", "", wski18n.T("`CFG_FILE` containing API configuration in swagger JSON format"))
+    apiCreateCmdV2.Flags().StringVar(&flags.api.resptype, "response-type", "json", wski18n.T("Set the web action response `TYPE`. Possible values are html, http, json, text, svg"))
+    apiGetCmdV2.Flags().BoolVarP(&flags.common.detail, "full", "f", false, wski18n.T("display full API configuration details"))
+    apiGetCmdV2.Flags().StringVarP(&flags.common.format, "format", "", formatOptionJson, wski18n.T("Specify the API output `TYPE`, either json or yaml"))
+    apiListCmdV2.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
+    apiListCmdV2.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
+    apiListCmdV2.Flags().BoolVarP(&flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
+    apiCmd.AddCommand(
+        apiCreateCmdV2,
+        apiGetCmdV2,
+        apiDeleteCmdV2,
+        apiListCmdV2,
+    )
+}
diff --git a/tools/cli/go-whisk-cli/commands/commands.go b/tools/cli/go-whisk-cli/commands/commands.go
new file mode 100644
index 0000000..6180c37
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/commands.go
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+    "net/http"
+    "os"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/spf13/cobra"
+)
+
+var client *whisk.Client
+const DefaultOpenWhiskApiPath string = "/api"
+
+func setupClientConfig(cmd *cobra.Command, args []string) (error){
+    baseURL, err := getURLBase(Properties.APIHost, DefaultOpenWhiskApiPath)
+
+    // Determine if the parent command will require the API host to be set
+    apiHostRequired := (cmd.Parent().Name() == "property" && cmd.Name() == "get" && (flags.property.auth ||
+      flags.property.apihost || flags.property.namespace || flags.property.apiversion || flags.property.cliversion)) ||
+      (cmd.Parent().Name() == "property" && cmd.Name() == "set" && (len(flags.property.apihostSet) > 0 ||
+        len(flags.property.apiversionSet) > 0 || len(flags.global.auth) > 0)) ||
+      (cmd.Parent().Name() == "sdk" && cmd.Name() == "install" && len(args) > 0 && args[0] == "bashauto")
+
+    // Display an error if the parent command requires an API host to be set, and the current API host is not valid
+    if err != nil && !apiHostRequired {
+        whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) error: %s\n", Properties.APIHost, DefaultOpenWhiskApiPath, err)
+        errMsg := wski18n.T("The API host is not valid: {{.err}}", map[string]interface{}{"err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    clientConfig := &whisk.Config{
+        AuthToken:  Properties.Auth,
+        Namespace:  Properties.Namespace,
+        BaseURL:    baseURL,
+        Version:    Properties.APIVersion,
+        Insecure:   flags.global.insecure,
+        Host:       Properties.APIHost,
+    }
+
+    // Setup client
+    client, err = whisk.NewClient(http.DefaultClient, clientConfig)
+
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "whisk.NewClient(%#v, %#v) error: %s\n", http.DefaultClient, clientConfig, err)
+        errMsg := wski18n.T("Unable to initialize server connection: {{.err}}", map[string]interface{}{"err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    return nil
+}
+
+func init() {
+    var err error
+
+    err = loadProperties()
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "loadProperties() error: %s\n", err)
+        fmt.Println(err)
+        os.Exit(whisk.EXITCODE_ERR_GENERAL)
+    }
+}
+
+func getKeyValueArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
+    var whiskErr error
+    var key string
+    var value string
+
+    if len(args) - 1 >= argIndex + 2 {
+        key = args[argIndex + 1]
+        value = args[argIndex + 2]
+        parsedArgs = append(parsedArgs, getFormattedJSON(key, value))
+        args = append(args[:argIndex], args[argIndex + 3:]...)
+    } else {
+        whisk.Debug(whisk.DbgError, "Arguments for '%s' must be a key/value pair; args: %s", args[argIndex], args)
+        errMsg := wski18n.T("Arguments for '{{.arg}}' must be a key/value pair",
+            map[string]interface{}{"arg": args[argIndex]})
+        whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+            whisk.DISPLAY_USAGE)
+    }
+
+    return parsedArgs, args, whiskErr
+}
+
+func getValueFromArgs(args []string, argIndex int, parsedArgs []string) ([]string, []string, error) {
+    var whiskErr error
+
+    if len(args) - 1 >= argIndex + 1 {
+        parsedArgs = append(parsedArgs, args[argIndex + 1])
+        args = append(args[:argIndex], args[argIndex + 2:]...)
+    } else {
+        whisk.Debug(whisk.DbgError, "An argument must be provided for '%s'; args: %s", args[argIndex], args)
+        errMsg := wski18n.T("An argument must be provided for '{{.arg}}'",
+            map[string]interface{}{"arg": args[argIndex]})
+        whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+            whisk.DISPLAY_USAGE)
+    }
+
+    return parsedArgs, args, whiskErr
+}
+
+func parseArgs(args []string) ([]string, []string, []string, error) {
+    var paramArgs []string
+    var annotArgs []string
+    var whiskErr error
+
+    i := 0
+
+    for i < len(args) {
+        if args[i] == "-P" || args[i] == "--param-file" {
+            paramArgs, args, whiskErr = getValueFromArgs(args, i, paramArgs)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+                errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
+                    map[string]interface{}{"err": whiskErr})
+                whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+                    whisk.DISPLAY_USAGE)
+                return nil, nil, nil, whiskErr
+            }
+
+            filename := paramArgs[len(paramArgs) - 1]
+            paramArgs[len(paramArgs) - 1], whiskErr = readFile(filename)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
+                return nil, nil, nil, whiskErr
+            }
+        } else if args[i] == "-A" || args[i] == "--annotation-file" {
+            annotArgs, args, whiskErr = getValueFromArgs(args, i, annotArgs)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "getValueFromArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+                errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
+                    map[string]interface{}{"err": whiskErr})
+                whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+                    whisk.DISPLAY_USAGE)
+                return nil, nil, nil, whiskErr
+            }
+
+            filename := annotArgs[len(annotArgs) - 1]
+            annotArgs[len(annotArgs) - 1], whiskErr = readFile(filename)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", filename, whiskErr)
+                return nil, nil, nil, whiskErr
+            }
+        } else if args[i] == "-p" || args[i] == "--param" {
+            paramArgs, args, whiskErr = getKeyValueArgs(args, i, paramArgs)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+                errMsg := wski18n.T("The parameter arguments are invalid: {{.err}}",
+                    map[string]interface{}{"err": whiskErr})
+                whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+                    whisk.DISPLAY_USAGE)
+                return nil, nil, nil, whiskErr
+            }
+        } else if args[i] == "-a" || args[i] == "--annotation"{
+            annotArgs, args, whiskErr = getKeyValueArgs(args, i, annotArgs)
+            if whiskErr != nil {
+                whisk.Debug(whisk.DbgError, "getKeyValueArgs(%#v, %d) failed: %s\n", args, i, whiskErr)
+                errMsg := wski18n.T("The annotation arguments are invalid: {{.err}}",
+                    map[string]interface{}{"err": whiskErr})
+                whiskErr = whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+                    whisk.DISPLAY_USAGE)
+                return nil, nil, nil, whiskErr
+            }
+        } else {
+            i++
+        }
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Found param args '%s'.\n", paramArgs)
+    whisk.Debug(whisk.DbgInfo, "Found annotations args '%s'.\n", annotArgs)
+    whisk.Debug(whisk.DbgInfo, "Arguments with param args removed '%s'.\n", args)
+
+    return args, paramArgs, annotArgs, nil
+}
+
+func Execute() error {
+    var err error
+
+    whisk.Debug(whisk.DbgInfo, "wsk args: %#v\n", os.Args)
+    os.Args, flags.common.param, flags.common.annotation, err = parseArgs(os.Args)
+
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "parseParams(%s) failed: %s\n", os.Args, err)
+        errMsg := wski18n.T("Failed to parse arguments: {{.err}}", map[string]interface{}{"err":err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    return WskCmd.Execute()
+}
diff --git a/tools/cli/go-whisk-cli/commands/flags.go b/tools/cli/go-whisk-cli/commands/flags.go
new file mode 100644
index 0000000..2af878d
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/flags.go
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "os"
+)
+
+///////////
+// Flags //
+///////////
+
+const MEMORY_FLAG   = "memory"
+const LOG_SIZE_FLAG = "logsize"
+const TIMEOUT_FLAG  = "timeout"
+const WEB_FLAG      = "web"
+
+var cliDebug = os.Getenv("WSK_CLI_DEBUG")  // Useful for tracing init() code
+
+var flags struct {
+
+    global struct {
+        verbose     bool
+        debug       bool
+        auth        string
+        apihost     string
+        apiversion  string
+        insecure    bool
+    }
+
+    common struct {
+        blocking    bool
+        annotation  []string
+        annotFile   string
+        param       []string
+        paramFile   string
+        shared      string // AKA "public" or "publish"
+        skip        int  // skip first N records
+        limit       int  // return max N records
+        full        bool // return full records (docs=true for client request)
+        summary     bool
+        feed        string  // name of feed
+        detail      bool
+        format      string
+    }
+
+    property struct {
+        auth            bool
+        apihost         bool
+        apiversion      bool
+        namespace       bool
+        cliversion      bool
+        apibuild        bool
+        apibuildno      bool
+        insecure        bool
+        all             bool
+        apihostSet      string
+        apiversionSet   string
+        namespaceSet    string
+    }
+
+    action struct {
+        docker      bool
+        copy        bool
+        pipe        bool
+        web         string
+        sequence    bool
+        timeout     int
+        memory      int
+        logsize     int
+        result      bool
+        kind        string
+        main        string
+    }
+
+    activation struct {
+        action          string // retrieve results for this action
+        upto            int64  // retrieve results up to certain time
+        since           int64  // retrieve results after certain time
+        seconds         int    // stop polling for activation upda
+        sinceSeconds    int
+        sinceMinutes    int
+        sinceHours      int
+        sinceDays       int
+        exit            int
+    }
+
+    // rule
+    rule struct {
+        disable bool
+        summary bool
+    }
+
+    // trigger
+    trigger struct {
+        summary bool
+    }
+
+    // api
+    api struct {
+        action     string
+        path       string
+        verb       string
+        basepath   string
+        apiname    string
+        configfile string
+        resptype   string
+    }
+}
+
+func IsVerbose() bool {
+    return flags.global.verbose || IsDebug()
+}
+
+func IsDebug() bool {
+    return len(cliDebug) > 0 || flags.global.debug
+}
diff --git a/tools/cli/go-whisk-cli/commands/namespace.go b/tools/cli/go-whisk-cli/commands/namespace.go
new file mode 100644
index 0000000..cafed6f
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/namespace.go
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "fmt"
+    "errors"
+
+    "github.com/spf13/cobra"
+    "github.com/fatih/color"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+)
+
+// namespaceCmd represents the namespace command
+var namespaceCmd = &cobra.Command{
+    Use:   "namespace",
+    Short: wski18n.T("work with namespaces"),
+}
+
+var namespaceListCmd = &cobra.Command{
+    Use:   "list",
+    Short: wski18n.T("list available namespaces"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        // add "TYPE" --> public / private
+
+        if whiskErr := checkArgs(args, 0, 0, "Namespace list", wski18n.T("No arguments are required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        namespaces, _, err := client.Namespaces.List()
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Namespaces.List() error: %s\n", err)
+            errStr := wski18n.T("Unable to obtain the list of available namespaces: {{.err}}",
+                map[string]interface{}{"err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+        printList(namespaces)
+        return nil
+    },
+}
+
+var namespaceGetCmd = &cobra.Command{
+    Use:   "get [NAMESPACE]",
+    Short: wski18n.T("get triggers, actions, and rules in the registry for a namespace"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var qName QualifiedName
+        var err error
+
+        if whiskErr := checkArgs(args, 0, 1, "Namespace get",
+                wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        // Namespace argument is optional; defaults to configured property namespace
+        if len(args) == 1 {
+            qName, err = parseQualifiedName(args[0])
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+                errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                        map[string]interface{}{"name": args[0], "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        namespace, _, err := client.Namespaces.Get(qName.namespace)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Namespaces.Get(%s) error: %s\n", getClientNamespace(), err)
+            errStr := wski18n.T("Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}",
+                    map[string]interface{}{"namespace": getClientNamespace(), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output, wski18n.T("Entities in namespace: {{.namespace}}\n",
+            map[string]interface{}{"namespace": boldString(getClientNamespace())}))
+        printList(namespace.Contents.Packages)
+        printList(namespace.Contents.Actions)
+        printList(namespace.Contents.Triggers)
+        printList(namespace.Contents.Rules)
+
+        return nil
+    },
+}
+
+var listCmd = &cobra.Command{
+    Use:   "list",
+    Short: wski18n.T("list entities in the current namespace"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE:   namespaceGetCmd.RunE,
+}
+
+func init() {
+    namespaceCmd.AddCommand(
+        namespaceListCmd,
+        namespaceGetCmd,
+    )
+}
diff --git a/tools/cli/go-whisk-cli/commands/package.go b/tools/cli/go-whisk-cli/commands/package.go
new file mode 100644
index 0000000..6020b72
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/package.go
@@ -0,0 +1,581 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+  "errors"
+  "fmt"
+  "net/http"
+
+  "../../go-whisk/whisk"
+  "../wski18n"
+
+  "github.com/fatih/color"
+  "github.com/spf13/cobra"
+)
+
+var packageCmd = &cobra.Command{
+  Use:   "package",
+  Short: wski18n.T("work with packages"),
+}
+
+var packageBindCmd = &cobra.Command{
+  Use:           "bind PACKAGE_NAME BOUND_PACKAGE_NAME",
+  Short:         wski18n.T("bind parameters to a package"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+
+    if whiskErr := checkArgs(args, 2, 2, "Package bind",
+            wski18n.T("A package name and binding name are required.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    packageName := args[0]
+    pkgQName, err := parseQualifiedName(packageName)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", packageName, err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": packageName, "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    bindingName := args[1]
+    bindQName, err := parseQualifiedName(bindingName)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", bindingName, err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": bindingName, "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    client.Namespace = bindQName.namespace
+
+    // Convert the binding's list of default parameters from a string into []KeyValue
+    // The 1 or more --param arguments have all been combined into a single []string
+    // e.g.   --p arg1,arg2 --p arg3,arg4   ->  [arg1, arg2, arg3, arg4]
+
+    whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+    parameters, err := getJSONFromStrings(flags.common.param, true)
+
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+      errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+          map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    // Convert the binding's list of default annotations from a string into []KeyValue
+    // The 1 or more --annotation arguments have all been combined into a single []string
+    // e.g.   --a arg1,arg2 --a arg3,arg4   ->  [arg1, arg2, arg3, arg4]
+    whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+    annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+      errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+          map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    binding := whisk.Binding{
+      Name:      pkgQName.entityName,
+      Namespace: pkgQName.namespace,
+    }
+
+    p := &whisk.BindingPackage{
+      Name:        bindQName.entityName,
+      Annotations: annotations.(whisk.KeyValueArr),
+      Parameters:  parameters.(whisk.KeyValueArr),
+      Binding:     binding,
+    }
+
+    _, _, err = client.Packages.Insert(p, false)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, false) failed: %s\n", p, err)
+      errStr := wski18n.T("Binding creation failed: {{.err}}", map[string]interface{}{"err":err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created binding {{.name}}\n",
+      map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(bindingName)}))
+    return nil
+  },
+}
+
+var packageCreateCmd = &cobra.Command{
+  Use:           "create PACKAGE_NAME",
+  Short:         wski18n.T("create a new package"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+    var shared, sharedSet bool
+
+    if whiskErr := checkArgs(args, 1, 1, "Package create", wski18n.T("A package name is required.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    qName, err := parseQualifiedName(args[0])
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": args[0], "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+    client.Namespace = qName.namespace
+
+    if shared, sharedSet, err = parseShared(flags.common.shared); err != nil {
+      whisk.Debug(whisk.DbgError, "parseShared(%s) failed: %s\n", flags.common.shared, err)
+      return err
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+    parameters, err := getJSONFromStrings(flags.common.param, true)
+
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+      errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+          map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+    annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+      errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+          map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    p := &whisk.Package{
+      Name:        qName.entityName,
+      Namespace:   qName.namespace,
+      Annotations: annotations.(whisk.KeyValueArr),
+      Parameters:  parameters.(whisk.KeyValueArr),
+    }
+
+    if sharedSet {
+      p.Publish = &shared
+    }
+
+    p, _, err = client.Packages.Insert(p, false)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, false) failed: %s\n", p, err)
+      errStr := wski18n.T(
+        "Unable to create package '{{.name}}': {{.err}}",
+        map[string]interface{}{
+          "name": qName.entityName,
+          "err": err,
+        })
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created package {{.name}}\n",
+      map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(qName.entityName)}))
+    return nil
+  },
+}
+
+var packageUpdateCmd = &cobra.Command{
+  Use:           "update PACKAGE_NAME",
+  Short:         wski18n.T("update an existing package, or create a package if it does not exist"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+    var shared, sharedSet bool
+
+    if whiskErr := checkArgs(args, 1, 1, "Package update", wski18n.T("A package name is required.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    qName, err := parseQualifiedName(args[0])
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": args[0], "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+    client.Namespace = qName.namespace
+
+    if shared, sharedSet, err = parseShared(flags.common.shared); err != nil {
+      whisk.Debug(whisk.DbgError, "parseShared(%s) failed: %s\n", flags.common.shared, err)
+      return err
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+    parameters, err := getJSONFromStrings(flags.common.param, true)
+
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+      errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+          map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+    annotations, err := getJSONFromStrings(flags.common.annotation, true)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+      errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+          map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+      return werr
+    }
+
+    p := &whisk.Package{
+      Name:        qName.entityName,
+      Namespace:   qName.namespace,
+      Annotations: annotations.(whisk.KeyValueArr),
+      Parameters:  parameters.(whisk.KeyValueArr),
+    }
+
+    if sharedSet {
+      p.Publish = &shared
+    }
+
+    p, _, err = client.Packages.Insert(p, true)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Insert(%#v, true) failed: %s\n", p, err)
+      errStr := wski18n.T("Package update failed: {{.err}}", map[string]interface{}{"err":err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    fmt.Fprintf(color.Output, wski18n.T("{{.ok}} updated package {{.name}}\n",
+      map[string]interface{}{"ok": color.GreenString(wski18n.T("ok:")), "name":boldString(qName.entityName)}))
+    return nil
+  },
+}
+
+var packageGetCmd = &cobra.Command{
+  Use:           "get PACKAGE_NAME [FIELD_FILTER]",
+  Short:         wski18n.T("get package"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+    var field string
+
+    if whiskErr := checkArgs(args, 1, 2, "Package get", wski18n.T("A package name is required.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    if len(args) > 1 {
+      field = args[1]
+
+      if !fieldExists(&whisk.Package{}, field) {
+        errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+          whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return whiskErr
+      }
+    }
+
+    qName, err := parseQualifiedName(args[0])
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": args[0], "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+    client.Namespace = qName.namespace
+
+    xPackage, _, err := client.Packages.Get(qName.entityName)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Get(%s) failed: %s\n", qName.entityName, err)
+      errStr := wski18n.T(
+        "Unable to get package '{{.name}}': {{.err}}",
+        map[string]interface{}{
+          "name": qName.entityName,
+          "err":err,
+        })
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    if flags.common.summary {
+      printSummary(xPackage)
+    } else {
+
+      if len(field) > 0 {
+        fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got package {{.name}}, displaying field {{.field}}\n",
+          map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName),
+          "field": boldString(field)}))
+        printField(xPackage, field)
+      } else {
+        fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got package {{.name}}\n",
+          map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+        printJSON(xPackage)
+      }
+    }
+
+    return nil
+  },
+}
+
+var packageDeleteCmd = &cobra.Command{
+  Use:           "delete PACKAGE_NAME",
+  Short:         wski18n.T("delete package"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+
+    if whiskErr := checkArgs(args, 1, 1, "Package delete", wski18n.T("A package name is required.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    qName, err := parseQualifiedName(args[0])
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+      errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+          map[string]interface{}{"name": args[0], "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+        whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+    client.Namespace = qName.namespace
+
+    _, err = client.Packages.Delete(qName.entityName)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Delete(%s) failed: %s\n", qName.entityName, err)
+      errStr := wski18n.T(
+        "Unable to delete package '{{.name}}': {{.err}}",
+        map[string]interface{}{
+          "name": qName.entityName,
+          "err": err,
+        })
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    fmt.Fprintf(color.Output,
+      wski18n.T("{{.ok}} deleted package {{.name}}\n",
+        map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+    return nil
+  },
+}
+
+var packageListCmd = &cobra.Command{
+  Use:           "list [NAMESPACE]",
+  Short:         wski18n.T("list all packages"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+    var shared bool
+
+    qName := QualifiedName{}
+    if len(args) == 1 {
+      qName, err = parseQualifiedName(args[0])
+      if err != nil {
+        whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+        errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+            map[string]interface{}{"name": args[0], "err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+          whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+      }
+      ns := qName.namespace
+      if len(ns) == 0 {
+        whisk.Debug(whisk.DbgError, "An empty namespace in the package name '%s' is invalid \n", args[0])
+        errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return werr
+      }
+
+      client.Namespace = ns
+    } else if whiskErr := checkArgs(args, 0, 1, "Package list",
+        wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+      return whiskErr
+    }
+
+    if flags.common.shared == "yes" {
+      shared = true
+    } else {
+      shared = false
+    }
+
+    options := &whisk.PackageListOptions{
+      Skip:   flags.common.skip,
+      Limit:  flags.common.limit,
+      Public: shared,
+    }
+
+    packages, _, err := client.Packages.List(options)
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.List(%+v) failed: %s\n", options, err)
+      errStr := wski18n.T("Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}",
+          map[string]interface{}{"name": getClientNamespace(), "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    printList(packages)
+    return nil
+  },
+}
+
+var packageRefreshCmd = &cobra.Command{
+  Use:           "refresh [NAMESPACE]",
+  Short:         wski18n.T("refresh package bindings"),
+  SilenceUsage:  true,
+  SilenceErrors: true,
+  PreRunE:       setupClientConfig,
+  RunE: func(cmd *cobra.Command, args []string) error {
+    var err error
+    var qName QualifiedName
+
+    if whiskErr := checkArgs(args, 0, 1, "Package refresh",
+        wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+      return whiskErr
+    } else {
+      if len(args) == 0 {
+        qName.namespace = getNamespace()
+      } else {
+        qName, err = parseQualifiedName(args[0])
+
+        if err != nil {
+          whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+          errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+              map[string]interface{}{"name": args[0], "err": err})
+          werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+          return werr
+        }
+      }
+    }
+
+    currentNamespace := client.Config.Namespace
+    client.Config.Namespace = qName.namespace
+
+    defer func() {
+      client.Config.Namespace = currentNamespace
+    }()
+
+    updates, resp, err := client.Packages.Refresh()
+    if err != nil {
+      whisk.Debug(whisk.DbgError, "client.Packages.Refresh() of namespace '%s' failed: %s\n", client.Config.Namespace, err)
+      errStr := wski18n.T("Package refresh for namespace '{{.name}}' failed: {{.err}}",
+          map[string]interface{}{"name": client.Config.Namespace, "err": err})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+    whisk.Debug(whisk.DbgInfo, "Refresh updates received: %#v\n", updates)
+
+    switch resp.StatusCode {
+    case http.StatusOK:
+      fmt.Printf(wski18n.T("{{.name}} refreshed successfully\n",
+        map[string]interface{}{"name": client.Config.Namespace}))
+
+      fmt.Println(wski18n.T("created bindings:"))
+
+      if len(updates.Added) > 0 {
+        printArrayContents(updates.Added)
+      }
+
+      fmt.Println(wski18n.T("updated bindings:"))
+
+      if len(updates.Updated) > 0 {
+        printArrayContents(updates.Updated)
+      }
+
+      fmt.Println(wski18n.T("deleted bindings:"))
+
+      if len(updates.Deleted) > 0 {
+        printArrayContents(updates.Deleted)
+      }
+
+    case http.StatusNotImplemented:
+      whisk.Debug(whisk.DbgError, "client.Packages.Refresh() for namespace '%s' returned 'Not Implemented' HTTP status code: %d\n", client.Config.Namespace, resp.StatusCode)
+      errStr := wski18n.T("The package refresh feature is not implemented in the target deployment")
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    default:
+      whisk.Debug(whisk.DbgError, "client.Packages.Refresh() for namespace '%s' returned an unexpected HTTP status code: %d\n", client.Config.Namespace, resp.StatusCode)
+      errStr := wski18n.T("Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}",
+          map[string]interface{}{"name": client.Config.Namespace, "code": resp.StatusCode})
+      werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+      return werr
+    }
+
+    return nil
+  },
+}
+
+func init() {
+  packageCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+  packageCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+  packageCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+  packageCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+  packageCreateCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("package visibility `SCOPE`; yes = shared, no = private"))
+
+  packageUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+  packageUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+  packageUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+  packageUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+  packageUpdateCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("package visibility `SCOPE`; yes = shared, no = private"))
+
+  packageGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize package details"))
+
+  packageBindCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+  packageBindCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+  packageBindCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+  packageBindCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+  packageListCmd.Flags().StringVar(&flags.common.shared, "shared", "", wski18n.T("include publicly shared entities in the result"))
+  packageListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of packages from the result"))
+  packageListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of packages from the collection"))
+
+  packageCmd.AddCommand(
+    packageBindCmd,
+    packageCreateCmd,
+    packageUpdateCmd,
+    packageGetCmd,
+    packageDeleteCmd,
+    packageListCmd,
+    packageRefreshCmd,
+  )
+}
diff --git a/tools/cli/go-whisk-cli/commands/property.go b/tools/cli/go-whisk-cli/commands/property.go
new file mode 100644
index 0000000..7fe91e7
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/property.go
@@ -0,0 +1,486 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+    "os"
+
+    "github.com/mitchellh/go-homedir"
+    "github.com/spf13/cobra"
+    "github.com/fatih/color"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+)
+
+var Properties struct {
+    Auth       string
+    APIHost    string
+    APIVersion string
+    APIBuild   string
+    APIBuildNo string
+    CLIVersion string
+    Namespace  string
+    PropsFile  string
+}
+
+const DefaultAuth       string = ""
+const DefaultAPIHost    string = ""
+const DefaultAPIVersion string = "v1"
+const DefaultAPIBuild   string = ""
+const DefaultAPIBuildNo string = ""
+const DefaultNamespace  string = "_"
+const DefaultPropsFile  string = "~/.wskprops"
+
+var propertyCmd = &cobra.Command{
+    Use:   "property",
+    Short: wski18n.T("work with whisk properties"),
+}
+
+//
+// Set one or more openwhisk property values
+//
+var propertySetCmd = &cobra.Command{
+    Use:            "set",
+    Short:          wski18n.T("set property"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var okMsg string = ""
+        var werr *whisk.WskError = nil
+
+        // get current props
+        props, err := readProps(Properties.PropsFile)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+            errStr := wski18n.T("Unable to set the property value: {{.err}}", map[string]interface{}{"err": err})
+            werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // read in each flag, update if necessary
+
+        if auth := flags.global.auth; len(auth) > 0 {
+            props["AUTH"] = auth
+            client.Config.AuthToken = auth
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:")}))
+        }
+
+        if apiHost := flags.property.apihostSet; len(apiHost) > 0 {
+            baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
+
+            if err != nil {
+                // Not aborting now.  Subsequent commands will result in error
+                whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) error: %s", apiHost, DefaultOpenWhiskApiPath, err)
+                errStr := fmt.Sprintf(
+                    wski18n.T("Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}",
+                        map[string]interface{}{"apihost": apiHost, "err": err}))
+                werr = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            } else {
+                props["APIHOST"] = apiHost
+                client.Config.BaseURL = baseURL
+                okMsg += fmt.Sprintf(
+                    wski18n.T("{{.ok}} whisk API host set to {{.host}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "host": boldString(apiHost)}))
+            }
+        }
+
+        if apiVersion := flags.property.apiversionSet; len(apiVersion) > 0 {
+            props["APIVERSION"] = apiVersion
+            client.Config.Version = apiVersion
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk API version set to {{.version}}\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:"), "version": boldString(apiVersion)}))
+        }
+
+        if namespace := flags.property.namespaceSet; len(namespace) > 0 {
+            namespaces, _, err := client.Namespaces.List()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Namespaces.List() failed: %s\n", err)
+                errStr := fmt.Sprintf(
+                    wski18n.T("Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}",
+                        map[string]interface{}{"name": namespace, "err": err}))
+                werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            } else {
+                whisk.Debug(whisk.DbgInfo, "Validating namespace '%s' is in user namespace list %#v\n", namespace, namespaces)
+                var validNamespace bool
+                for _, ns := range namespaces {
+                    if ns.Name == namespace {
+                        whisk.Debug(whisk.DbgInfo, "Namespace '%s' is valid\n", namespace)
+                        validNamespace = true
+                    }
+                }
+                if !validNamespace {
+                    whisk.Debug(whisk.DbgError, "Namespace '%s' is not in the list of entitled namespaces\n", namespace)
+                    errStr := fmt.Sprintf(
+                        wski18n.T("Namespace '{{.name}}' is not in the list of entitled namespaces",
+                            map[string]interface{}{"name": namespace}))
+                    werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                } else {
+                    props["NAMESPACE"] = namespace
+                    okMsg += fmt.Sprintf(
+                        wski18n.T("{{.ok}} whisk namespace set to {{.name}}\n",
+                            map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(namespace)}))
+                }
+            }
+        }
+
+        err = writeProps(Properties.PropsFile, props)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
+            errStr := fmt.Sprintf(
+                wski18n.T("Unable to set the property value(s): {{.err}}",
+                    map[string]interface{}{"err": err}))
+            werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        } else {
+            fmt.Fprintf(color.Output, okMsg)
+        }
+
+        if (werr != nil) {
+            return werr
+        }
+
+        return nil
+    },
+}
+
+var propertyUnsetCmd = &cobra.Command{
+    Use:            "unset",
+    Short:          wski18n.T("unset property"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var okMsg string = ""
+        props, err := readProps(Properties.PropsFile)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+            errStr := fmt.Sprintf(
+                wski18n.T("Unable to unset the property value: {{.err}}",
+                    map[string]interface{}{"err": err}))
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // read in each flag, update if necessary
+
+        if flags.property.auth {
+            delete(props, "AUTH")
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk auth unset.\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:")}))
+        }
+
+        if flags.property.namespace {
+            delete(props, "NAMESPACE")
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk namespace unset",
+                    map[string]interface{}{"ok": color.GreenString("ok:")}))
+            if len(DefaultNamespace) > 0 {
+                okMsg += fmt.Sprintf(
+                    wski18n.T("; the default value of {{.default}} will be used.\n",
+                        map[string]interface{}{"default": boldString(DefaultNamespace)}))
+            } else {
+                okMsg += fmt.Sprint(
+                    wski18n.T("; there is no default value that can be used.\n"))
+            }
+        }
+
+        if flags.property.apihost {
+            delete(props, "APIHOST")
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk API host unset.\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:")}))
+        }
+
+        if flags.property.apiversion {
+            delete(props, "APIVERSION")
+            okMsg += fmt.Sprintf(
+                wski18n.T("{{.ok}} whisk API version unset",
+                    map[string]interface{}{"ok": color.GreenString("ok:")}))
+            if len(DefaultAPIVersion) > 0 {
+                okMsg += fmt.Sprintf(
+                    wski18n.T("; the default value of {{.default}} will be used.\n",
+                        map[string]interface{}{"default": boldString(DefaultAPIVersion)}))
+            } else {
+                okMsg += fmt.Sprint(
+                    wski18n.T("; there is no default value that can be used.\n"))
+            }
+        }
+
+        err = writeProps(Properties.PropsFile, props)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
+            errStr := fmt.Sprintf(
+                wski18n.T("Unable to unset the property value: {{.err}}",
+                    map[string]interface{}{"err": err}))
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output, okMsg)
+        if err = loadProperties(); err != nil {
+            whisk.Debug(whisk.DbgError, "loadProperties() failed: %s\n", err)
+        }
+        return nil
+    },
+}
+
+var propertyGetCmd = &cobra.Command{
+    Use:            "get",
+    Short:          wski18n.T("get property"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+
+        // If no property is explicitly specified, default to all properties
+        if !(flags.property.all || flags.property.auth ||
+             flags.property.apiversion || flags.property.cliversion ||
+             flags.property.namespace || flags.property.apibuild ||
+             flags.property.apihost || flags.property.apibuildno) {
+            flags.property.all = true
+        }
+
+        if flags.property.all || flags.property.auth {
+            fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk auth"), boldString(Properties.Auth))
+        }
+
+        if flags.property.all || flags.property.apihost {
+            fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk API host"), boldString(Properties.APIHost))
+        }
+
+        if flags.property.all || flags.property.apiversion {
+            fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk API version"), boldString(Properties.APIVersion))
+        }
+
+        if flags.property.all || flags.property.namespace {
+            fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk namespace"), boldString(Properties.Namespace))
+        }
+
+        if flags.property.all || flags.property.cliversion {
+            fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk CLI version"), boldString(Properties.CLIVersion))
+        }
+
+        if flags.property.all || flags.property.apibuild || flags.property.apibuildno {
+            info, _, err := client.Info.Get()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Info.Get() failed: %s\n", err)
+                info = &whisk.Info{}
+                info.Build = wski18n.T("Unknown")
+                info.BuildNo = wski18n.T("Unknown")
+            }
+            if flags.property.all || flags.property.apibuild {
+                fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk API build"), boldString(info.Build))
+            }
+            if flags.property.all || flags.property.apibuildno {
+                fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk API build number"), boldString(info.BuildNo))
+            }
+            if err != nil {
+                errStr := fmt.Sprintf(
+                    wski18n.T("Unable to obtain API build information: {{.err}}",
+                        map[string]interface{}{"err": err}))
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        return nil
+    },
+}
+
+func init() {
+    propertyCmd.AddCommand(
+        propertySetCmd,
+        propertyUnsetCmd,
+        propertyGetCmd,
+    )
+
+    // need to set property flags as booleans instead of strings... perhaps with boolApihost...
+    propertyGetCmd.Flags().BoolVar(&flags.property.auth, "auth", false, wski18n.T("authorization key"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, wski18n.T("whisk API host"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, wski18n.T("whisk API version"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.apibuild, "apibuild", false, wski18n.T("whisk API build version"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.apibuildno, "apibuildno", false, wski18n.T("whisk API build number"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.cliversion, "cliversion", false, wski18n.T("whisk CLI version"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
+    propertyGetCmd.Flags().BoolVar(&flags.property.all, "all", false, wski18n.T("all properties"))
+
+    propertySetCmd.Flags().StringVarP(&flags.global.auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
+    propertySetCmd.Flags().StringVar(&flags.property.apihostSet, "apihost", "", wski18n.T("whisk API `HOST`"))
+    propertySetCmd.Flags().StringVar(&flags.property.apiversionSet, "apiversion", "", wski18n.T("whisk API `VERSION`"))
+    propertySetCmd.Flags().StringVar(&flags.property.namespaceSet, "namespace", "", wski18n.T("whisk `NAMESPACE`"))
+
+    propertyUnsetCmd.Flags().BoolVar(&flags.property.auth, "auth", false, wski18n.T("authorization key"))
+    propertyUnsetCmd.Flags().BoolVar(&flags.property.apihost, "apihost", false, wski18n.T("whisk API host"))
+    propertyUnsetCmd.Flags().BoolVar(&flags.property.apiversion, "apiversion", false, wski18n.T("whisk API version"))
+    propertyUnsetCmd.Flags().BoolVar(&flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
+
+}
+
+func setDefaultProperties() {
+    Properties.Auth = DefaultAuth
+    Properties.Namespace = DefaultNamespace
+    Properties.APIHost = DefaultAPIHost
+    Properties.APIBuild = DefaultAPIBuild
+    Properties.APIBuildNo = DefaultAPIBuildNo
+    Properties.APIVersion = DefaultAPIVersion
+    Properties.PropsFile = DefaultPropsFile
+    // Properties.CLIVersion value is set from main's init()
+}
+
+func getPropertiesFilePath() (propsFilePath string, werr error) {
+    var envExists bool
+
+    // Environment variable overrides the default properties file path
+    if propsFilePath, envExists = os.LookupEnv("WSK_CONFIG_FILE"); envExists == true || propsFilePath != "" {
+        whisk.Debug(whisk.DbgInfo, "Using properties file '%s' from WSK_CONFIG_FILE environment variable\n", propsFilePath)
+        return propsFilePath, nil
+    } else {
+        var err error
+
+        propsFilePath, err = homedir.Expand(Properties.PropsFile)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "homedir.Expand(%s) failed: %s\n", Properties.PropsFile, err)
+            errStr := fmt.Sprintf(
+                wski18n.T("Unable to locate properties file '{{.filename}}': {{.err}}",
+                    map[string]interface{}{"filename": Properties.PropsFile, "err": err}))
+            werr = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return propsFilePath, werr
+        }
+
+        whisk.Debug(whisk.DbgInfo, "Using properties file home dir '%s'\n", propsFilePath)
+    }
+
+    return propsFilePath, nil
+}
+
+func loadProperties() error {
+    var err error
+
+    setDefaultProperties()
+
+    Properties.PropsFile, err = getPropertiesFilePath()
+    if err != nil {
+        return nil
+        //whisk.Debug(whisk.DbgError, "getPropertiesFilePath() failed: %s\n", err)
+        //errStr := fmt.Sprintf("Unable to load the properties file: %s", err)
+        //werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        //return werr
+    }
+
+    props, err := readProps(Properties.PropsFile)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+        errStr := wski18n.T("Unable to read the properties file '{{.filename}}': {{.err}}",
+                map[string]interface{}{"filename": Properties.PropsFile, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    if authToken, hasProp := props["AUTH"]; hasProp {
+        Properties.Auth = authToken
+    }
+
+    if authToken := os.Getenv("WHISK_AUTH"); len(authToken) > 0 {
+        Properties.Auth = authToken
+    }
+
+    if apiVersion, hasProp := props["APIVERSION"]; hasProp {
+        Properties.APIVersion = apiVersion
+    }
+
+    if apiVersion := os.Getenv("WHISK_APIVERSION"); len(apiVersion) > 0 {
+        Properties.APIVersion = apiVersion
+    }
+
+    if apiHost, hasProp := props["APIHOST"]; hasProp {
+        Properties.APIHost = apiHost
+    }
+
+    if apiHost := os.Getenv("WHISK_APIHOST"); len(apiHost) > 0 {
+        Properties.APIHost = apiHost
+    }
+
+    if namespace, hasProp := props["NAMESPACE"]; hasProp && len(namespace) > 0 {
+        Properties.Namespace = namespace
+    }
+
+    if namespace := os.Getenv("WHISK_NAMESPACE"); len(namespace) > 0 {
+        Properties.Namespace = namespace
+    }
+
+    return nil
+}
+
+func parseConfigFlags(cmd *cobra.Command, args []string) error {
+
+    if auth := flags.global.auth; len(auth) > 0 {
+        Properties.Auth = auth
+        if client != nil {
+            client.Config.AuthToken = auth
+        }
+    }
+
+    if namespace := flags.property.namespaceSet; len(namespace) > 0 {
+        Properties.Namespace = namespace
+        if client != nil {
+            client.Config.Namespace = namespace
+        }
+    }
+
+    if apiVersion := flags.global.apiversion; len(apiVersion) > 0 {
+        Properties.APIVersion = apiVersion
+        if client != nil {
+            client.Config.Version = apiVersion
+        }
+    }
+
+    if apiHost := flags.global.apihost; len(apiHost) > 0 {
+        Properties.APIHost = apiHost
+
+        if client != nil {
+            client.Config.Host = apiHost
+            baseURL, err := getURLBase(apiHost, DefaultOpenWhiskApiPath)
+
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "getURLBase(%s, %s) failed: %s\n", apiHost, DefaultOpenWhiskApiPath, err)
+                errStr := wski18n.T("Invalid host address '{{.host}}': {{.err}}",
+                        map[string]interface{}{"host": Properties.APIHost, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            client.Config.BaseURL = baseURL
+        }
+    }
+
+    if flags.global.debug {
+        whisk.SetDebug(true)
+    }
+    if flags.global.verbose {
+        whisk.SetVerbose(true)
+    }
+
+    return nil
+}
+
diff --git a/tools/cli/go-whisk-cli/commands/rule.go b/tools/cli/go-whisk-cli/commands/rule.go
new file mode 100644
index 0000000..e29b677
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/rule.go
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/fatih/color"
+    "github.com/spf13/cobra"
+)
+
+// ruleCmd represents the rule command
+var ruleCmd = &cobra.Command{
+    Use:   "rule",
+    Short: wski18n.T("work with rules"),
+}
+
+var ruleEnableCmd = &cobra.Command{
+    Use:   "enable RULE_NAME",
+    Short: wski18n.T("enable rule"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 1, "Rule enable", wski18n.T("A rule name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+
+        _, _, err = client.Rules.SetState(ruleName, "active")
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, active) failed: %s\n", ruleName, err)
+            errStr := wski18n.T("Unable to enable rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} enabled rule {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+        return nil
+    },
+}
+
+var ruleDisableCmd = &cobra.Command{
+    Use:   "disable RULE_NAME",
+    Short: wski18n.T("disable rule"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 1, "Rule disable", wski18n.T("A rule name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+
+        _, _, err = client.Rules.SetState(ruleName, "inactive")
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
+            errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} disabled rule {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+        return nil
+    },
+}
+
+var ruleStatusCmd = &cobra.Command{
+    Use:   "status RULE_NAME",
+    Short: wski18n.T("get rule status"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 1, "Rule status", wski18n.T("A rule name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+
+        rule, _, err := client.Rules.Get(ruleName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.Get(%s) failed: %s\n", ruleName, err)
+            errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} rule {{.name}} is {{.status}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName), "status": boldString(rule.Status)}))
+        return nil
+    },
+}
+
+var ruleCreateCmd = &cobra.Command{
+    Use:   "create RULE_NAME TRIGGER_NAME ACTION_NAME",
+    Short: wski18n.T("create new rule"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 3, 3, "Rule create",
+                wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+        triggerName := getQualifiedName(args[1], Properties.Namespace)
+        actionName := getQualifiedName(args[2], Properties.Namespace)
+
+        rule := &whisk.Rule{
+            Name:    ruleName,
+            Trigger: triggerName,
+            Action:  actionName,
+        }
+
+        whisk.Debug(whisk.DbgInfo, "Inserting rule:\n%+v\n", rule)
+        var retRule *whisk.Rule
+        retRule, _, err = client.Rules.Insert(rule, false)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.Insert(%#v) failed: %s\n", rule, err)
+            errStr := wski18n.T("Unable to create rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+        whisk.Debug(whisk.DbgInfo, "Inserted rule:\n%+v\n", retRule)
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} created rule {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+        return nil
+    },
+}
+
+var ruleUpdateCmd = &cobra.Command{
+    Use:   "update RULE_NAME TRIGGER_NAME ACTION_NAME",
+    Short: wski18n.T("update an existing rule, or create a rule if it does not exist"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 3, 3, "Rule update",
+                wski18n.T("A rule, trigger and action name are required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+        triggerName := getQualifiedName(args[1], Properties.Namespace)
+        actionName := getQualifiedName(args[2], Properties.Namespace)
+
+        rule := &whisk.Rule{
+            Name:    ruleName,
+            Trigger: triggerName,
+            Action:  actionName,
+        }
+
+        _, _, err = client.Rules.Insert(rule, true)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.Insert(%#v) failed: %s\n", rule, err)
+            errStr := wski18n.T("Unable to update rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": rule.Name, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} updated rule {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+        return nil
+    },
+}
+
+var ruleGetCmd = &cobra.Command{
+    Use:   "get RULE_NAME",
+    Short: wski18n.T("get rule"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var field string
+
+        if whiskErr := checkArgs(args, 1, 2, "Rule get", wski18n.T("A rule name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if len(args) > 1 {
+            field = args[1]
+
+            if !fieldExists(&whisk.Rule{}, field){
+                errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+                whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+
+        rule, _, err := client.Rules.Get(ruleName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.Get(%s) failed: %s\n", ruleName, err)
+            errStr := wski18n.T("Unable to get rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        if (flags.rule.summary) {
+            printRuleSummary(rule)
+        } else {
+            if len(field) > 0 {
+                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}, displaying field {{.field}}\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName),
+                        "field": field}))
+                printField(rule, field)
+            } else {
+                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got rule {{.name}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+                printJSON(rule)
+            }
+        }
+
+        return nil
+    },
+}
+
+var ruleDeleteCmd = &cobra.Command{
+    Use:   "delete RULE_NAME",
+    Short: wski18n.T("delete rule"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 1, "Rule delete", wski18n.T("A rule name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        client.Namespace = qName.namespace
+        ruleName := qName.entityName
+
+        if flags.rule.disable {
+            _, _, err := client.Rules.SetState(ruleName, "inactive")
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "client.Rules.SetState(%s, inactive) failed: %s\n", ruleName, err)
+                errStr := wski18n.T("Unable to disable rule '{{.name}}': {{.err}}",
+                        map[string]interface{}{"name": ruleName, "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        _, err = client.Rules.Delete(ruleName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.Delete(%s) error: %s\n", ruleName, err)
+            errStr := wski18n.T("Unable to delete rule '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": ruleName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} deleted rule {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(ruleName)}))
+        return nil
+    },
+}
+
+var ruleListCmd = &cobra.Command{
+    Use:   "list [NAMESPACE]",
+    Short: wski18n.T("list all rules"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        qName := QualifiedName{}
+
+        if len(args) == 1 {
+            qName, err = parseQualifiedName(args[0])
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+                errStr := wski18n.T("Namespace '{{.name}}' is invalid: {{.err}}\n",
+                        map[string]interface{}{"name": args[0], "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            ns := qName.namespace
+            if len(ns) == 0 {
+                whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", args[0])
+                errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+            client.Namespace = ns
+        } else if whiskErr := checkArgs(args, 0, 1, "Rule list",
+                wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        ruleListOptions := &whisk.RuleListOptions{
+            Skip:  flags.common.skip,
+            Limit: flags.common.limit,
+        }
+
+        rules, _, err := client.Rules.List(ruleListOptions)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Rules.List(%#v) error: %s\n", ruleListOptions, err)
+            errStr := wski18n.T("Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": getClientNamespace(), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+        printList(rules)
+        return nil
+    },
+}
+
+func init() {
+    ruleDeleteCmd.Flags().BoolVar(&flags.rule.disable, "disable", false, wski18n.T("automatically disable rule before deleting it"))
+
+    ruleGetCmd.Flags().BoolVarP(&flags.rule.summary, "summary", "s", false, wski18n.T("summarize rule details"))
+
+    ruleListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of rules from the result"))
+    ruleListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of rules from the collection"))
+
+    ruleCmd.AddCommand(
+        ruleCreateCmd,
+        ruleEnableCmd,
+        ruleDisableCmd,
+        ruleStatusCmd,
+        ruleUpdateCmd,
+        ruleGetCmd,
+        ruleDeleteCmd,
+        ruleListCmd,
+    )
+
+}
diff --git a/tools/cli/go-whisk-cli/commands/sdk.go b/tools/cli/go-whisk-cli/commands/sdk.go
new file mode 100644
index 0000000..4fc042b
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/sdk.go
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+    "io"
+    "os"
+    "strings"
+
+    "github.com/spf13/cobra"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+)
+
+// sdkCmd represents the sdk command
+var sdkCmd = &cobra.Command{
+    Use:   "sdk",
+    Short: wski18n.T("work with the sdk"),
+}
+
+type sdkInfo struct {
+    UrlPath   string
+    FileName  string
+    isGzTar   bool
+    IsGzip    bool
+    IsZip     bool
+    IsTar     bool
+    Unpack    bool
+    UnpackDir string
+}
+
+var sdkMap map[string]*sdkInfo
+const SDK_DOCKER_COMPONENT_NAME string = "docker"
+const SDK_IOS_COMPONENT_NAME string = "ios"
+const BASH_AUTOCOMPLETE_FILENAME string = "wsk_cli_bash_completion.sh"
+
+var sdkInstallCmd = &cobra.Command{
+    Use:   "install COMPONENT",
+    Short: wski18n.T("install SDK artifacts"),
+    Long: wski18n.T("install SDK artifacts, where valid COMPONENT values are docker, ios, and bashauto"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        if len(args) != 1 {
+            whisk.Debug(whisk.DbgError, "Invalid number of arguments: %d\n", len(args))
+            errStr := wski18n.T("The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified")
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+        component := strings.ToLower(args[0])
+        switch component {
+        case "docker":
+            err = dockerInstall()
+        case "ios":
+            err = iOSInstall()
+        case "bashauto":
+            err = WskCmd.GenBashCompletionFile(BASH_AUTOCOMPLETE_FILENAME)
+            if (err != nil) {
+                whisk.Debug(whisk.DbgError, "GenBashCompletionFile('%s`) error: \n", BASH_AUTOCOMPLETE_FILENAME, err)
+                errStr := wski18n.T("Unable to generate '{{.name}}': {{.err}}",
+                        map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            fmt.Printf(
+                wski18n.T("bash_completion_msg",
+                    map[string]interface{}{"name": BASH_AUTOCOMPLETE_FILENAME}))
+        default:
+            whisk.Debug(whisk.DbgError, "Invalid component argument '%s'\n", component)
+            errStr := wski18n.T("The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto",
+                    map[string]interface{}{"component": component})
+            err = whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        }
+
+        if err != nil {
+            return err
+        }
+        return nil
+    },
+}
+
+func dockerInstall() error {
+    var err error
+
+    targetFile := sdkMap[SDK_DOCKER_COMPONENT_NAME].FileName
+    if _, err = os.Stat(targetFile); err == nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
+        errStr := wski18n.T("The file {{.name}} already exists.  Delete it and retry.",
+            map[string]interface{}{"name": targetFile})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    if err = sdkInstall(SDK_DOCKER_COMPONENT_NAME); err != nil {
+        whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_DOCKER_COMPONENT_NAME, err)
+        errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
+                map[string]interface{}{"component": SDK_DOCKER_COMPONENT_NAME, "err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    fmt.Println(wski18n.T("The docker skeleton is now installed at the current directory."))
+    return nil
+}
+
+func iOSInstall() error {
+    var err error
+
+    if err = sdkInstall(SDK_IOS_COMPONENT_NAME); err != nil {
+        whisk.Debug(whisk.DbgError, "sdkInstall(%s) failed: %s\n", SDK_IOS_COMPONENT_NAME, err)
+        errStr := wski18n.T("The {{.component}} SDK installation failed: {{.err}}",
+                map[string]interface{}{"component": SDK_IOS_COMPONENT_NAME, "err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    fmt.Printf(
+        wski18n.T("Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n",
+            map[string]interface{}{"name": sdkMap[SDK_IOS_COMPONENT_NAME].FileName}))
+    return nil
+}
+
+func sdkInstall(componentName string) error {
+    targetFile := sdkMap[componentName].FileName
+    if _, err := os.Stat(targetFile); err == nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", targetFile)
+        errStr := wski18n.T("The file {{.name}} already exists.  Delete it and retry.",
+                map[string]interface{}{"name": targetFile})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    resp, err := client.Sdks.Install(sdkMap[componentName].UrlPath)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "client.Sdks.Install(%s) failed: %s\n", sdkMap[componentName].UrlPath, err)
+        errStr := wski18n.T("Unable to retrieve '{{.urlpath}}' SDK: {{.err}}",
+                map[string]interface{}{"urlpath": sdkMap[componentName].UrlPath, "err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    if resp.Body == nil {
+        whisk.Debug(whisk.DbgError, "SDK Install HTTP response has no body\n")
+        errStr := wski18n.T("Server failed to send the '{{.component}}' SDK: {{.err}}",
+                map[string]interface{}{"name": componentName, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    // Create the SDK file
+    sdkfile, err := os.Create(targetFile)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Create(%s) failure: %s\n", targetFile, err)
+        errStr := wski18n.T("Error creating SDK file {{.name}}: {{.err}}",
+                map[string]interface{}{"name": targetFile, "err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    // Read the HTTP response body and write it to the SDK file
+    whisk.Debug(whisk.DbgInfo, "Reading SDK file from HTTP response body\n")
+    _, err = io.Copy(sdkfile, resp.Body)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "io.Copy() of resp.Body into sdkfile failure: %s\n", err)
+        errStr := wski18n.T("Error copying server response into file: {{.err}}",
+                map[string]interface{}{"err": err})
+        werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        sdkfile.Close()
+        return werr
+
+    }
+    sdkfile.Close()     // Don't use 'defer' since this file might need to be deleted after unpack
+
+    // At this point, the entire file is downloaded from the server
+    // Check if there is any special post-download processing (i.e. unpack)
+    if sdkMap[componentName].Unpack {
+        // Make sure the target directory does not already exist
+        defer os.Remove(targetFile)
+        targetdir := sdkMap[componentName].UnpackDir
+        if _, err = os.Stat(targetdir); err == nil {
+            whisk.Debug(whisk.DbgError, "os.Stat reports that directory '%s' exists\n", targetdir)
+            errStr := wski18n.T("The directory {{.name}} already exists.  Delete it and retry.",
+                    map[string]interface{}{"name": targetdir})
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // If the packed SDK is a .tgz file, unpack it in two steps
+        // 1. UnGzip into temp .tar file
+        // 2. Untar the contents into the current folder
+        if sdkMap[componentName].isGzTar {
+            whisk.Debug(whisk.DbgInfo, "unGzipping downloaded file\n")
+            err := unpackGzip(targetFile, "temp.tar")
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "unpackGzip(%s,temp.tar) failure: %s\n", targetFile, err)
+                errStr := wski18n.T("Error unGzipping file {{.name}}: {{.err}}",
+                    map[string]interface{}{"name": targetFile, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            defer os.Remove("temp.tar")
+
+            whisk.Debug(whisk.DbgInfo, "unTarring unGzipped file\n")
+            err = unpackTar("temp.tar")
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "unpackTar(temp.tar) failure: %s\n", err)
+                errStr := wski18n.T("Error untarring file {{.name}}: {{.err}}",
+                        map[string]interface{}{"name": "temp.tar", "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        // Future SDKs may require other unpacking procedures not yet covered here....
+    }
+
+    return nil
+}
+
+func init() {
+    sdkCmd.AddCommand(sdkInstallCmd)
+
+    sdkMap = make(map[string]*sdkInfo)
+    sdkMap["docker"] = &sdkInfo{ UrlPath: "blackbox-0.1.0.tar.gz", FileName: "blackbox-0.1.0.tar.gz", isGzTar: true, Unpack: true, UnpackDir: "dockerSkeleton"}
+    sdkMap["ios"] = &sdkInfo{ UrlPath: "OpenWhiskIOSStarterApp.zip", FileName: "OpenWhiskIOSStarterApp.zip", IsZip: true, Unpack: false}
+}
diff --git a/tools/cli/go-whisk-cli/commands/trigger.go b/tools/cli/go-whisk-cli/commands/trigger.go
new file mode 100644
index 0000000..2978aa7
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/trigger.go
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "errors"
+    "fmt"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/spf13/cobra"
+    "github.com/fatih/color"
+)
+
+const FEED_LIFECYCLE_EVENT  = "lifecycleEvent"
+const FEED_TRIGGER_NAME     = "triggerName"
+const FEED_AUTH_KEY         = "authKey"
+const FEED_CREATE           = "CREATE"
+const FEED_DELETE           = "DELETE"
+
+// triggerCmd represents the trigger command
+var triggerCmd = &cobra.Command{
+    Use:   "trigger",
+    Short: wski18n.T("work with triggers"),
+}
+
+var triggerFireCmd = &cobra.Command{
+    Use:   "fire TRIGGER_NAME [PAYLOAD]",
+    Short: wski18n.T("fire trigger event"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var parameters interface{}
+
+        if whiskErr := checkArgs(args, 1, 2, "Trigger fire",
+                wski18n.T("A trigger name is required. A payload is optional.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+            return whiskErr
+        }
+
+        client.Namespace = qName.namespace
+
+        // Add payload to parameters
+        if len(args) == 2 {
+            flags.common.param = append(flags.common.param, getFormattedJSON("payload", args[1]))
+            flags.common.param = append(flags.common.param, flags.common.param...)
+        }
+
+        if len(flags.common.param) > 0 {
+            parameters, err = getJSONFromStrings(flags.common.param, false)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, false) failed: %s\n", flags.common.param, err)
+                errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+                        map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        trigResp, _, err := client.Triggers.Fire(qName.entityName, parameters)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Fire(%s, %#v) failed: %s\n", qName.entityName, parameters, err)
+            errStr := wski18n.T("Unable to fire trigger '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": qName.entityName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n",
+                map[string]interface{}{
+                    "ok": color.GreenString("ok:"),
+                    "namespace": boldString(qName.namespace),
+                    "name": boldString(qName.entityName),
+                    "id": boldString(trigResp.ActivationId)}))
+        return nil
+    },
+}
+
+var triggerCreateCmd = &cobra.Command{
+    Use:   "create TRIGGER_NAME",
+    Short: wski18n.T("create new trigger"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var annotations interface{}
+        var feedArgPassed bool = (flags.common.feed != "")
+
+        if whiskErr := checkArgs(args, 1, 1, "Trigger create",
+                wski18n.T("A trigger name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+
+        client.Namespace = qName.namespace
+
+        var fullTriggerName string
+        var fullFeedName string
+        if feedArgPassed {
+            whisk.Debug(whisk.DbgInfo, "Trigger has a feed\n")
+            feedqName, err := parseQualifiedName(flags.common.feed)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", flags.common.feed, err)
+                errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                        map[string]interface{}{"name": flags.common.feed, "err": err})
+                whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return whiskErr
+            }
+            if len(feedqName.namespace) == 0 {
+                whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", flags.common.feed)
+                errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+
+            fullFeedName = fmt.Sprintf("/%s/%s", feedqName.namespace, feedqName.entityName)
+            fullTriggerName = fmt.Sprintf("/%s/%s", qName.namespace, qName.entityName)
+            flags.common.param = append(flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_CREATE))
+            flags.common.param = append(flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName))
+            flags.common.param = append(flags.common.param, getFormattedJSON(FEED_AUTH_KEY, client.Config.AuthToken))
+        }
+
+
+        // Convert the trigger's list of default parameters from a string into []KeyValue
+        // The 1 or more --param arguments have all been combined into a single []string
+        // e.g.   --p arg1,arg2 --p arg3,arg4   ->  [arg1, arg2, arg3, arg4]
+        whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+        parameters, err := getJSONFromStrings(flags.common.param, !feedArgPassed)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+            errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+                    map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        // Add feed to annotations
+        if feedArgPassed {
+            flags.common.annotation = append(flags.common.annotation, getFormattedJSON("feed", flags.common.feed))
+        }
+
+        whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+        annotations, err = getJSONFromStrings(flags.common.annotation, true)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+            errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+                    map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        trigger := &whisk.Trigger{
+            Name:        qName.entityName,
+            Annotations: annotations.(whisk.KeyValueArr),
+        }
+
+        if !feedArgPassed {
+            trigger.Parameters = parameters.(whisk.KeyValueArr)
+        }
+
+        _, _, err = client.Triggers.Insert(trigger, false)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Insert(%+v,false) failed: %s\n", trigger, err)
+            errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": trigger.Name, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // Invoke the specified feed action to configure the trigger feed
+        if feedArgPassed {
+            err := configureFeed(trigger.Name, fullFeedName)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", trigger.Name, flags.common.feed,
+                    err)
+                errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}",
+                        map[string]interface{}{"name": trigger.Name, "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+                // Delete trigger that was created for this feed
+                delerr := deleteTrigger(args[0])
+                if delerr != nil {
+                    whisk.Debug(whisk.DbgWarn, "Ignoring deleteTrigger(%s) failure: %s\n", args[0], delerr)
+                }
+                return werr
+            }
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} created trigger {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)}))
+        return nil
+    },
+}
+
+var triggerUpdateCmd = &cobra.Command{
+    Use:   "update TRIGGER_NAME",
+    Short: wski18n.T("update an existing trigger, or create a trigger if it does not exist"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+
+        if whiskErr := checkArgs(args, 1, 1, "Trigger update",
+                wski18n.T("A trigger name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+            return whiskErr
+        }
+
+        client.Namespace = qName.namespace
+
+        // Convert the trigger's list of default parameters from a string into []KeyValue
+        // The 1 or more --param arguments have all been combined into a single []string
+        // e.g.   --p arg1,arg2 --p arg3,arg4   ->  [arg1, arg2, arg3, arg4]
+
+        whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", flags.common.param)
+        parameters, err := getJSONFromStrings(flags.common.param, true)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.param, err)
+            errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
+                    map[string]interface{}{"param": fmt.Sprintf("%#v",flags.common.param), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", flags.common.annotation)
+        annotations, err := getJSONFromStrings(flags.common.annotation, true)
+
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", flags.common.annotation, err)
+            errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
+                    map[string]interface{}{"annotation": fmt.Sprintf("%#v",flags.common.annotation), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return werr
+        }
+
+        trigger := &whisk.Trigger{
+            Name:        qName.entityName,
+            Parameters:  parameters.(whisk.KeyValueArr),
+            Annotations: annotations.(whisk.KeyValueArr),
+        }
+
+        _, _, err = client.Triggers.Insert(trigger, true)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Insert(%+v,true) failed: %s\n", trigger, err)
+            errStr := wski18n.T("Unable to update trigger '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": trigger.Name, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} updated trigger {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)}))
+        return nil
+    },
+}
+
+var triggerGetCmd = &cobra.Command{
+    Use:   "get TRIGGER_NAME [FIELD_FILTER]",
+    Short: wski18n.T("get trigger"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var field string
+
+        if whiskErr := checkArgs(args, 1, 2, "Trigger get", wski18n.T("A trigger name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        if len(args) > 1 {
+            field = args[1]
+
+            if !fieldExists(&whisk.Trigger{}, field) {
+                errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field})
+                whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+                    whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return whiskErr
+            }
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+            return whiskErr
+        }
+
+        client.Namespace = qName.namespace
+
+        retTrigger, _, err := client.Triggers.Get(qName.entityName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Get(%s) failed: %s\n", qName.entityName, err)
+            errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": qName.entityName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        if (flags.trigger.summary) {
+            printSummary(retTrigger)
+        } else {
+            if len(field) > 0 {
+                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
+                    map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName),
+                    "field": boldString(field)}))
+                printField(retTrigger, field)
+            } else {
+                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+                printJSON(retTrigger)
+            }
+        }
+
+        return nil
+    },
+}
+
+var triggerDeleteCmd = &cobra.Command{
+    Use:   "delete TRIGGER_NAME",
+    Short: wski18n.T("delete trigger"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        var retTrigger *whisk.Trigger
+        var fullFeedName string
+        var origParams []string
+
+        if whiskErr := checkArgs(args, 1, 1, "Trigger delete",
+                wski18n.T("A trigger name is required.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        qName, err := parseQualifiedName(args[0])
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+            errMsg := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                    map[string]interface{}{"name": args[0], "err": err})
+            whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+                whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+
+            return whiskErr
+        }
+
+        client.Namespace = qName.namespace
+
+        retTrigger, _, err = client.Triggers.Get(qName.entityName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Get(%s) failed: %s\n", qName.entityName, err)
+            errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": qName.entityName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        // Get full feed name from trigger delete request as it is needed to delete the feed
+        if retTrigger != nil && retTrigger.Annotations != nil {
+            fullFeedName = getValueString(retTrigger.Annotations, "feed")
+
+            if len(fullFeedName) > 0 {
+                origParams = flags.common.param
+                fullTriggerName := fmt.Sprintf("/%s/%s", qName.namespace, qName.entityName)
+                flags.common.param = append(flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_DELETE))
+                flags.common.param = append(flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName))
+                flags.common.param = append(flags.common.param, getFormattedJSON(FEED_AUTH_KEY, client.Config.AuthToken))
+
+                err = configureFeed(qName.entityName, fullFeedName)
+                if err != nil {
+                    whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qName.entityName, fullFeedName, err)
+                }
+
+                flags.common.param = origParams
+                client.Namespace = qName.namespace
+            }
+
+        }
+
+        retTrigger, _, err = client.Triggers.Delete(qName.entityName)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.Delete(%s) failed: %s\n", qName.entityName, err)
+            errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": qName.entityName, "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        fmt.Fprintf(color.Output,
+            wski18n.T("{{.ok}} deleted trigger {{.name}}\n",
+                map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qName.entityName)}))
+
+        return nil
+    },
+}
+
+var triggerListCmd = &cobra.Command{
+    Use:   "list [NAMESPACE]",
+    Short: wski18n.T("list all triggers"),
+    SilenceUsage:   true,
+    SilenceErrors:  true,
+    PreRunE: setupClientConfig,
+    RunE: func(cmd *cobra.Command, args []string) error {
+        var err error
+        qName := QualifiedName{}
+        if len(args) == 1 {
+            qName, err = parseQualifiedName(args[0])
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", args[0], err)
+                errStr := wski18n.T("'{{.name}}' is not a valid qualified name: {{.err}}",
+                        map[string]interface{}{"name": args[0], "err": err})
+                werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+            ns := qName.namespace
+            if len(ns) == 0 {
+                whisk.Debug(whisk.DbgError, "Namespace is missing from '%s'\n", args[0])
+                errStr := wski18n.T("No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"")
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+                return werr
+            }
+            client.Namespace = ns
+            whisk.Debug(whisk.DbgInfo, "Using namespace '%s' from argument '%s''\n", ns, args[0])
+        } else if whiskErr := checkArgs(args, 0, 1, "Trigger list",
+                wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
+            return whiskErr
+        }
+
+        options := &whisk.TriggerListOptions{
+            Skip:  flags.common.skip,
+            Limit: flags.common.limit,
+        }
+        triggers, _, err := client.Triggers.List(options)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "client.Triggers.List(%#v) for namespace '%s' failed: %s\n", options,
+                client.Namespace, err)
+            errStr := wski18n.T("Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": getClientNamespace(), "err": err})
+            werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+        printList(triggers)
+        return nil
+    },
+}
+
+func configureFeed(triggerName string, FullFeedName string) error {
+    feedArgs := []string {FullFeedName}
+    flags.common.blocking = true
+    err := actionInvokeCmd.RunE(nil, feedArgs)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "Invoke of action '%s' failed: %s\n", FullFeedName, err)
+        errStr := wski18n.T("Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}",
+                map[string]interface{}{"trigname": triggerName, "feedname": FullFeedName, "err": err})
+        err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+    } else {
+        whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", FullFeedName)
+    }
+
+    return err
+}
+
+func deleteTrigger(triggerName string) error {
+    args := []string {triggerName}
+    err := triggerDeleteCmd.RunE(nil, args)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "Trigger '%s' delete failed: %s\n", triggerName, err)
+        errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": triggerName, "err": err})
+        err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+    }
+
+    return err
+}
+
+func init() {
+    triggerCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+    triggerCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+    triggerCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+    triggerCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+    triggerCreateCmd.Flags().StringVarP(&flags.common.feed, "feed", "f", "", wski18n.T("trigger feed `ACTION_NAME`"))
+
+    triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
+    triggerUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
+    triggerUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+    triggerUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+    triggerGetCmd.Flags().BoolVarP(&flags.trigger.summary, "summary", "s", false, wski18n.T("summarize trigger details"))
+
+    triggerFireCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
+    triggerFireCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
+
+    triggerListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of triggers from the result"))
+    triggerListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of triggers from the collection"))
+
+    triggerCmd.AddCommand(
+        triggerFireCmd,
+        triggerCreateCmd,
+        triggerUpdateCmd,
+        triggerGetCmd,
+        triggerDeleteCmd,
+        triggerListCmd,
+    )
+
+}
diff --git a/tools/cli/go-whisk-cli/commands/util.go b/tools/cli/go-whisk-cli/commands/util.go
new file mode 100644
index 0000000..b63efdc
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/util.go
@@ -0,0 +1,1069 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "bufio"
+    "errors"
+    "fmt"
+    "strings"
+
+    "../../go-whisk/whisk"
+    "../wski18n"
+
+    "github.com/fatih/color"
+    //prettyjson "github.com/hokaccha/go-prettyjson"  // See prettyjson comment below
+    "archive/tar"
+    "io"
+    "os"
+    "compress/gzip"
+    "archive/zip"
+    "encoding/json"
+    "net/url"
+    "io/ioutil"
+    "sort"
+    "reflect"
+    "bytes"
+)
+
+type QualifiedName struct {
+    namespace   string  // namespace. does not include leading '/'.  may be "" (i.e. default namespace)
+    packageName string  // package.  may be "".  does not include leading/trailing '/'
+    entity      string  // entity.  should not be ""
+    entityName  string  // pkg+entity
+}
+
+func (qName QualifiedName) String() string {
+    output := []string{}
+
+    if len(qName.namespace) > 0 {
+        output = append(output, "/", qName.namespace, "/")
+    }
+    if len(qName.packageName) > 0 {
+        output = append(output, qName.packageName, "/")
+    }
+    output = append(output, qName.entityName)
+
+    return strings.Join(output, "")
+}
+
+/*
+Parse a (possibly fully qualified) resource name into namespace and name components. If the given qualified name isNone,
+then this is a default qualified name and it is resolved from properties. If the namespace is missing from the qualified
+name, the namespace is also resolved from the property file.
+
+Return a qualifiedName struct
+
+Examples:
+      foo => qName {namespace: "_", entityName: foo}
+      pkg/foo => qName {namespace: "_", entityName: pkg/foo}
+      /ns/foo => qName {namespace: ns, entityName: foo}
+      /ns/pkg/foo => qName {namespace: ns, entityName: pkg/foo}
+*/
+func parseQualifiedName(name string) (QualifiedName, error) {
+    var qualifiedName QualifiedName
+
+    // If name has a preceding delimiter (/), it contains a namespace. Otherwise the name does not specify a namespace,
+    // so default the namespace to the namespace value set in the properties file; if that is not set, use "_"
+    if  strings.HasPrefix(name, "/")  {
+        parts := strings.Split(name, "/")
+        qualifiedName.namespace = parts[1]
+
+        if len(parts) < 2 || len(parts) > 4 {
+            whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+            errStr := wski18n.T("A valid qualified name must be specified.")
+            err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return qualifiedName, err
+        }
+
+        for i := 1; i < len(parts); i++ {
+            if len(parts[i]) == 0 || parts[i] == "." {
+                whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+                errStr := wski18n.T("A valid qualified name must be specified.")
+                err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return qualifiedName, err
+            }
+        }
+
+        qualifiedName.entityName = strings.Join(parts[2:], "/")
+        if len(parts) == 4 {
+            qualifiedName.packageName = parts[2]
+        }
+        qualifiedName.entity = parts[len(parts)-1]
+    } else {
+        if len(name) == 0 || name == "." {
+            whisk.Debug(whisk.DbgError, "A valid qualified name was not detected\n")
+            errStr := wski18n.T("A valid qualified name must be specified.")
+            err := whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return qualifiedName, err
+        }
+
+        parts := strings.Split(name, "/")
+        qualifiedName.entity = parts[len(parts)-1]
+        if len(parts) == 2 {
+            qualifiedName.packageName = parts[0]
+        }
+        qualifiedName.entityName = name
+        qualifiedName.namespace = getNamespace()
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Qualified pkg+entity (EntityName): %s\n", qualifiedName.entityName)
+    whisk.Debug(whisk.DbgInfo, "Qualified namespace: %s\n", qualifiedName.namespace)
+    whisk.Debug(whisk.DbgInfo, "Qualified package: %s\n", qualifiedName.packageName)
+    whisk.Debug(whisk.DbgInfo, "Qualified entity: %s\n", qualifiedName.entity)
+
+    return qualifiedName, nil
+}
+
+func getNamespace() (string) {
+    namespace := "_"
+
+    if Properties.Namespace != "" {
+        namespace = Properties.Namespace
+    }
+
+    return namespace
+}
+
+/*
+Return a fully qualified name given a (possibly fully qualified) resource name and optional namespace.
+
+Examples:
+      (foo, None) => /_/foo
+      (pkg/foo, None) => /_/pkg/foo
+      (foo, ns) => /ns/foo
+      (/ns/pkg/foo, None) => /ns/pkg/foo
+      (/ns/pkg/foo, otherns) => /ns/pkg/foo
+*/
+func getQualifiedName(name string, namespace string) (string) {
+    if strings.HasPrefix(name, "/") {
+        return name
+    } else if strings.HasPrefix(namespace, "/")  {
+        return fmt.Sprintf("%s/%s", namespace, name)
+    } else {
+        if len(namespace) == 0 {
+            namespace = Properties.Namespace
+        }
+        return fmt.Sprintf("/%s/%s", namespace, name)
+    }
+}
+
+func csvToQualifiedActions(artifacts string) ([]string) {
+    var res []string
+    actions := strings.Split(artifacts, ",")
+    for i := 0; i < len(actions); i++ {
+        res = append(res, getQualifiedName(actions[i], Properties.Namespace))
+    }
+
+    return res
+}
+
+func getJSONFromStrings(content []string, keyValueFormat bool) (interface{}, error) {
+    var data map[string]interface{}
+    var res interface{}
+
+    whisk.Debug(whisk.DbgInfo, "Convert content to JSON: %#v\n", content)
+
+    for i := 0; i < len(content); i++ {
+        dc := json.NewDecoder(strings.NewReader(content[i]))
+        dc.UseNumber()
+        if err := dc.Decode(&data); err!=nil {
+            whisk.Debug(whisk.DbgError, "Invalid JSON detected for '%s' \n", content[i])
+            return whisk.KeyValueArr{} , err
+        }
+
+        whisk.Debug(whisk.DbgInfo, "Created map '%v' from '%v'\n", data, content[i])
+    }
+
+    if keyValueFormat {
+        res = getKeyValueFormattedJSON(data)
+    } else {
+        res = data
+    }
+
+    return res, nil
+}
+
+func getKeyValueFormattedJSON(data map[string]interface{}) (whisk.KeyValueArr) {
+    var keyValueArr whisk.KeyValueArr
+
+    for key, value := range data {
+        keyValue := whisk.KeyValue{
+            Key:  key,
+            Value: value,
+        }
+        keyValueArr = append(keyValueArr, keyValue)
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Created key/value format '%v' from '%v'\n", keyValueArr, data)
+
+    return keyValueArr
+}
+
+func getFormattedJSON(key string, value string) (string) {
+    var res string
+
+    key = getEscapedJSON(key)
+
+    if isValidJSON(value) {
+        whisk.Debug(whisk.DbgInfo, "Value '%s' is valid JSON.\n", value)
+        res = fmt.Sprintf("{\"%s\": %s}", key, value)
+    } else {
+        whisk.Debug(whisk.DbgInfo, "Converting value '%s' to a string as it is not valid JSON.\n", value)
+        res = fmt.Sprintf("{\"%s\": \"%s\"}", key, value)
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Formatted JSON '%s'\n", res)
+
+    return res
+}
+
+func getEscapedJSON(value string) (string) {
+    value = strings.Replace(value, "\\", "\\\\", -1)
+    value = strings.Replace(value, "\"", "\\\"", -1)
+
+    return value
+}
+
+func isValidJSON(value string) (bool) {
+    var jsonInterface interface{}
+    err := json.Unmarshal([]byte(value), &jsonInterface)
+    return err == nil
+}
+
+var boldString = color.New(color.Bold).SprintFunc()
+
+func printList(collection interface{}) {
+    switch collection := collection.(type) {
+    case []whisk.Action:
+        printActionList(collection)
+    case []whisk.Trigger:
+        printTriggerList(collection)
+    case []whisk.Package:
+        printPackageList(collection)
+    case []whisk.Rule:
+        printRuleList(collection)
+    case []whisk.Namespace:
+        printNamespaceList(collection)
+    case []whisk.Activation:
+        printActivationList(collection)
+    case []whisk.Api:
+        printApiList(collection)
+    }
+}
+
+func printFullList(collection interface{}) {
+    switch collection := collection.(type) {
+    case []whisk.Action:
+
+    case []whisk.Trigger:
+
+    case []whisk.Package:
+
+    case []whisk.Rule:
+
+    case []whisk.Namespace:
+
+    case []whisk.Activation:
+        printFullActivationList(collection)
+    }
+}
+
+func printSummary(collection interface{}) {
+    switch collection := collection.(type) {
+    case *whisk.Action:
+        printActionSummary(collection)
+    case *whisk.Trigger:
+        printTriggerSummary(collection)
+    case *whisk.Package:
+        printPackageSummary(collection)
+    case *whisk.Rule:
+
+    case *whisk.Namespace:
+
+    case *whisk.Activation:
+    }
+}
+
+func printActionList(actions []whisk.Action) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("actions"))
+    for _, action := range actions {
+        publishState := wski18n.T("private")
+        kind := getValueString(action.Annotations, "exec")
+        fmt.Printf("%-70s %s %s\n", fmt.Sprintf("/%s/%s", action.Namespace, action.Name), publishState, kind)
+    }
+}
+
+func printTriggerList(triggers []whisk.Trigger) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("triggers"))
+    for _, trigger := range triggers {
+        publishState := wski18n.T("private")
+        fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", trigger.Namespace, trigger.Name), publishState)
+    }
+}
+
+func printPackageList(packages []whisk.Package) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("packages"))
+    for _, xPackage := range packages {
+        publishState := wski18n.T("private")
+        if xPackage.Publish != nil && *xPackage.Publish {
+            publishState = wski18n.T("shared")
+        }
+        fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", xPackage.Namespace, xPackage.Name), publishState)
+    }
+}
+
+func printRuleList(rules []whisk.Rule) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("rules"))
+    for _, rule := range rules {
+        publishState := wski18n.T("private")
+        fmt.Printf("%-70s %s\n", fmt.Sprintf("/%s/%s", rule.Namespace, rule.Name), publishState)
+    }
+}
+
+func printNamespaceList(namespaces []whisk.Namespace) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("namespaces"))
+    for _, namespace := range namespaces {
+        fmt.Printf("%s\n", namespace.Name)
+    }
+}
+
+func printActivationList(activations []whisk.Activation) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
+    for _, activation := range activations {
+        fmt.Printf("%s %-20s\n", activation.ActivationID, activation.Name)
+    }
+}
+
+func printFullActivationList(activations []whisk.Activation) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
+    for _, activation := range activations {
+        printJSON(activation)
+    }
+}
+
+func printApiList(apis []whisk.Api) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("apis"))
+    for _, api := range apis {
+        fmt.Printf("%s %20s %20s\n", api.ApiName, api.GatewayBasePath, api.GatewayFullPath)
+    }
+}
+
+func printFullApiList(apis []whisk.Api) {
+    fmt.Fprintf(color.Output, "%s\n", boldString("apis"))
+    for _, api := range apis {
+        printJSON(api)
+    }
+}
+
+func printActivationLogs(logs []string) {
+    for _, log := range logs {
+        fmt.Printf("%s\n", log)
+    }
+}
+
+func printArrayContents(arrStr []string) {
+    for _, str := range arrStr {
+        fmt.Printf("%s\n", str)
+    }
+}
+
+func printPackageSummary(pkg *whisk.Package) {
+    printEntitySummary(fmt.Sprintf("%7s", "package"), getFullName(pkg.Namespace, pkg.Name, ""),
+        getValueString(pkg.Annotations, "description"),
+        strings.Join(getChildValueStrings(pkg.Annotations, "parameters", "name"), ", "))
+
+
+    if pkg.Actions != nil {
+        for _, action := range pkg.Actions {
+            printEntitySummary(fmt.Sprintf("%7s", "action"), getFullName(pkg.Namespace, pkg.Name, action.Name),
+                getValueString(action.Annotations, "description"),
+                strings.Join(getChildValueStrings(action.Annotations, "parameters", "name"), ", "))
+        }
+    }
+
+    if pkg.Feeds != nil {
+        for _, feed := range pkg.Feeds {
+            printEntitySummary(fmt.Sprintf("%7s", "feed  "), getFullName(pkg.Namespace, pkg.Name, feed.Name),
+                getValueString(feed.Annotations, "description"),
+                strings.Join(getChildValueStrings(feed.Annotations, "parameters", "name"), ", "))
+        }
+    }
+}
+
+func printActionSummary(action *whisk.Action) {
+    printEntitySummary(fmt.Sprintf("%6s", "action"),
+        getFullName(action.Namespace, "", action.Name),
+        getValueString(action.Annotations, "description"),
+        strings.Join(getChildValueStrings(action.Annotations, "parameters", "name"), ", "))
+}
+
+func printTriggerSummary(trigger *whisk.Trigger) {
+    printEntitySummary(fmt.Sprintf("%7s", "trigger"),
+        getFullName(trigger.Namespace, "", trigger.Name),
+        getValueString(trigger.Annotations, "description"),
+        strings.Join(getChildValueStrings(trigger.Annotations, "parameters", "name"), ", "))
+}
+
+func printRuleSummary(rule *whisk.Rule) {
+    fmt.Fprintf(color.Output, "%s %s\n", boldString(fmt.Sprintf("%4s", "rule")),
+        getFullName(rule.Namespace, "", rule.Name))
+    fmt.Fprintf(color.Output, "   (%s: %s)\n", boldString(wski18n.T("status")), rule.Status)
+}
+
+func printEntitySummary(entityType string, fullName string, description string, params string) {
+    if len(description) > 0 {
+        fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, description)
+    } else {
+        fmt.Fprintf(color.Output, "%s %s\n", boldString(entityType), fullName)
+    }
+
+    if len(params) > 0 {
+        fmt.Fprintf(color.Output, "   (%s: %s)\n", boldString(wski18n.T("parameters")), params)
+    }
+}
+
+func getFullName(namespace string, packageName string, entityName string) (string) {
+    var fullName string
+
+    if len(namespace) > 0 && len(packageName) > 0 && len(entityName) > 0 {
+        fullName = fmt.Sprintf("/%s/%s/%s", namespace, packageName, entityName)
+    } else if len(namespace) > 0 && len(packageName) > 0 {
+        fullName = fmt.Sprintf("/%s/%s", namespace, packageName)
+    } else if len(namespace) > 0 && len(entityName) > 0 {
+        fullName = fmt.Sprintf("/%s/%s", namespace, entityName)
+    } else if len(namespace) > 0 {
+        fullName = fmt.Sprintf("/%s", namespace)
+    }
+
+    return fullName
+}
+
+func deleteKey(key string, keyValueArr whisk.KeyValueArr) (whisk.KeyValueArr) {
+    for i := 0; i < len(keyValueArr); i++ {
+        if keyValueArr[i].Key == key {
+            keyValueArr = append(keyValueArr[:i], keyValueArr[i + 1:]...)
+            break
+        }
+    }
+
+    return keyValueArr
+}
+
+func addKeyValue(key string, value interface{}, keyValueArr whisk.KeyValueArr) (whisk.KeyValueArr) {
+    keyValue := whisk.KeyValue{
+        Key:  key,
+        Value: value,
+    }
+
+    return append(keyValueArr, keyValue)
+}
+
+func getKeys(keyValueArr whisk.KeyValueArr) ([]string) {
+    var res []string
+
+    for i := 0; i < len(keyValueArr); i++ {
+        res = append(res, keyValueArr[i].Key)
+    }
+
+    sort.Strings(res)
+    whisk.Debug(whisk.DbgInfo, "Got keys '%v' from '%v'\n", res, keyValueArr)
+
+    return res
+}
+
+func getValue(keyValueArr whisk.KeyValueArr, key string) (interface{}) {
+    var res interface{}
+
+    for i := 0; i < len(keyValueArr); i++ {
+        if keyValueArr[i].Key == key {
+            res = keyValueArr[i].Value
+            break;
+        }
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Got value '%v' from '%v' for key '%s'\n", res, keyValueArr, key)
+
+    return res
+}
+
+func getValueString(keyValueArr whisk.KeyValueArr, key string) (string) {
+    var value interface{}
+    var res string
+
+    value = getValue(keyValueArr, key)
+    castedValue, canCast := value.(string)
+
+    if (canCast) {
+        res = castedValue
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Got string value '%v' for key '%s'\n", res, key)
+
+    return res
+}
+
+func getChildValues(keyValueArr whisk.KeyValueArr, key string, childKey string) ([]interface{}) {
+    var value interface{}
+    var res []interface{}
+
+    value = getValue(keyValueArr, key)
+
+    castedValue, canCast := value.([]interface{})
+    if canCast {
+        for i := 0; i < len(castedValue); i++ {
+            castedValue, canCast := castedValue[i].(map[string]interface{})
+            if canCast {
+                for subKey, subValue := range castedValue {
+                    if subKey == childKey {
+                        res = append(res, subValue)
+                    }
+                }
+            }
+        }
+    }
+
+    whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
+        childKey)
+
+    return res
+}
+
+func getChildValueStrings(keyValueArr whisk.KeyValueArr, key string, childKey string) ([]string) {
+    var keys []interface{}
+    var res []string
+
+    keys = getChildValues(keyValueArr, key, childKey)
+
+    for i := 0; i < len(keys); i++ {
+        castedValue, canCast := keys[i].(string)
+        if (canCast) {
+            res = append(res, castedValue)
+        }
+    }
+
+    sort.Strings(res)
+    whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
+        childKey)
+
+    return res
+}
+
+func getValueFromJSONResponse(field string, response map[string]interface {}) (interface{}) {
+    var res interface{}
+
+    for key, value := range response {
+        if key == field {
+            res = value
+            break
+        }
+    }
+
+    return res
+}
+
+func logoText() string {
+    logo := `
+        ____      ___                   _    _ _     _     _
+       /\   \    / _ \ _ __   ___ _ __ | |  | | |__ (_)___| | __
+  /\  /__\   \  | | | | '_ \ / _ \ '_ \| |  | | '_ \| / __| |/ /
+ /  \____ \  /  | |_| | |_) |  __/ | | | |/\| | | | | \__ \   <
+ \   \  /  \/    \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
+  \___\/ tm           |_|
+`
+
+    return logo
+}
+
+func printJSON(v interface{}, stream ...io.Writer) {
+    // Can't use prettyjson util issue  https://github.com/hokaccha/go-prettyjson/issues/1 is fixed
+    //output, _ := prettyjson.Marshal(v)
+    //
+    //if len(stream) > 0 {
+    //    fmt.Fprintf(stream[0], string(output))
+    //} else {
+    //    fmt.Fprintf(color.Output, string(output))
+    //}
+    printJsonNoColor(v, stream...)
+}
+
+func printJsonNoColor(decoded interface{}, stream ...io.Writer) {
+    var output bytes.Buffer
+
+    buffer := new(bytes.Buffer)
+    encoder := json.NewEncoder(buffer)
+    encoder.SetEscapeHTML(false)
+    encoder.Encode(&decoded)
+    json.Indent(&output, buffer.Bytes(), "", "    ")
+
+    if len(stream) > 0 {
+        fmt.Fprintf(stream[0], "%s", string(output.Bytes()))
+    } else {
+        fmt.Fprintf(os.Stdout, "%s", string(output.Bytes()))
+    }
+}
+
+func unpackGzip(inpath string, outpath string) error {
+    // Make sure the target file does not exist
+    if _, err := os.Stat(outpath); err == nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", outpath)
+        errStr := wski18n.T("The file {{.name}} already exists.  Delete it and retry.",
+            map[string]interface{}{"name": outpath})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    // Make sure the input file exists
+    if _, err := os.Stat(inpath); err != nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    unGzFile, err := os.Create(outpath)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", outpath, err)
+        errStr := wski18n.T("Error creating unGzip file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": outpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+    defer unGzFile.Close()
+
+    gzFile, err := os.Open(inpath)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
+        errStr := wski18n.T("Error opening Gzip file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": inpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+    defer gzFile.Close()
+
+    gzReader, err := gzip.NewReader(gzFile)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "gzip.NewReader() failed: %s\n", err)
+        errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": inpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    _, err = io.Copy(unGzFile, gzReader)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "io.Copy() failed: %s\n", err)
+        errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": inpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    return nil
+}
+
+func unpackZip(inpath string) error {
+    // Make sure the input file exists
+    if _, err := os.Stat(inpath); err != nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    zipFileReader, err := zip.OpenReader(inpath)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "zip.OpenReader(%s) failed: %s\n", inpath, err)
+        errStr := wski18n.T("Unable to opens '{{.name}}' for unzipping: {{.err}}",
+            map[string]interface{}{"name": inpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+    defer zipFileReader.Close()
+
+    // Loop through the files in the zipfile
+    for _, item := range zipFileReader.File {
+        itemName := item.Name
+        itemType := item.Mode()
+
+        whisk.Debug(whisk.DbgInfo, "file item - %#v\n", item)
+
+        if itemType.IsDir() {
+            if err := os.MkdirAll(item.Name, item.Mode()); err != nil {
+                whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode(), err)
+                errStr := wski18n.T("Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
+                        map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+
+        if itemType.IsRegular() {
+            unzipFile, err := item.Open()
+            defer unzipFile.Close()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "'%s' Open() failed: %s\n", item.Name, err)
+                errStr := wski18n.T("Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+                        map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            targetFile, err := os.Create(itemName)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", itemName, err)
+                errStr := wski18n.T("Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+                        map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            if _, err := io.Copy(targetFile, unzipFile); err != nil {
+                whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", itemName, err)
+                errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
+                        map[string]interface{}{"name": itemName, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        }
+    }
+
+    return nil
+}
+
+func unpackTar(inpath string) error {
+
+    // Make sure the input file exists
+    if _, err := os.Stat(inpath); err != nil {
+        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
+        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name": inpath})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+
+    tarFileReader, err := os.Open(inpath)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
+        errStr := wski18n.T("Error opening tar file '{{.name}}': {{.err}}",
+            map[string]interface{}{"name": inpath, "err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+    defer tarFileReader.Close()
+
+    // Loop through the files in the tarfile
+    tReader := tar.NewReader(tarFileReader)
+    for {
+        item, err := tReader.Next()
+        if err == io.EOF {
+            whisk.Debug(whisk.DbgError, "EOF reach during untar\n")
+            break  // end of tar
+        }
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "tReader.Next() failed: %s\n", err)
+            errStr := wski18n.T("Error reading tar file '{{.name}}': {{.err}}",
+                    map[string]interface{}{"name": inpath, "err": err})
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+
+        whisk.Debug(whisk.DbgInfo, "tar file item - %#v\n", item)
+        switch item.Typeflag {
+        case tar.TypeDir:
+            if err := os.MkdirAll(item.Name, os.FileMode(item.Mode)); err != nil {
+                whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode, err)
+                errStr := wski18n.T("Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
+                        map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        case tar.TypeReg:
+            untarFile, err:= os.OpenFile(item.Name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(item.Mode))
+            defer untarFile.Close()
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", item.Name, err)
+                errStr := wski18n.T("Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
+                        map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+            if _, err := io.Copy(untarFile, tReader); err != nil {
+                whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", item.Name, err)
+                errStr := wski18n.T("Unable to untar file '{{.name}}': {{.err}}",
+                        map[string]interface{}{"name": item.Name, "err": err})
+                werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+                return werr
+            }
+        default:
+            whisk.Debug(whisk.DbgError, "Unexpected tar file type of %q\n", item.Typeflag)
+            errStr := wski18n.T("Unable to untar '{{.name}}' due to unexpected tar file type\n",
+                    map[string]interface{}{"name": item.Name})
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+    }
+    return nil
+}
+
+func checkArgs(args []string, minimumArgNumber int, maximumArgNumber int, commandName string,
+    requiredArgMsg string) (*whisk.WskError) {
+        exactlyOrAtLeast := wski18n.T("exactly")
+        exactlyOrNoMoreThan := wski18n.T("exactly")
+
+    if (minimumArgNumber != maximumArgNumber) {
+        exactlyOrAtLeast = wski18n.T("at least")
+        exactlyOrNoMoreThan = wski18n.T("no more than")
+    }
+
+    if len(args) < minimumArgNumber {
+        whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
+            exactlyOrAtLeast, minimumArgNumber))
+        errMsg := wski18n.T("Invalid argument(s). {{.required}}", map[string]interface{}{"required": requiredArgMsg})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr
+    } else if len(args) > maximumArgNumber {
+        whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
+            exactlyOrNoMoreThan, maximumArgNumber))
+        errMsg := wski18n.T("Invalid argument(s): {{.args}}. {{.required}}",
+            map[string]interface{}{"args": strings.Join(args[maximumArgNumber:], ", "), "required": requiredArgMsg})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return whiskErr
+    } else {
+        return nil
+    }
+}
+
+func getURLBase(host string, path string) (*url.URL, error)  {
+    if len(host) == 0 {
+        errMsg := wski18n.T("An API host must be provided.")
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    urlBase := fmt.Sprintf("%s%s", host, path)
+    url, err := url.Parse(urlBase)
+
+    if len(url.Scheme) == 0 || len(url.Host) == 0 {
+        urlBase = fmt.Sprintf("https://%s%s", host, path)
+        url, err = url.Parse(urlBase)
+    }
+
+    return url, err
+}
+
+func normalizeNamespace(namespace string) (string) {
+    if (namespace == "_") {
+        namespace = wski18n.T("default")
+    }
+
+    return namespace
+}
+
+func getClientNamespace() (string) {
+    return normalizeNamespace(client.Config.Namespace)
+}
+
+func readFile(filename string) (string, error) {
+    _, err := os.Stat(filename)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Stat(%s) error: %s\n", filename, err)
+        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
+                map[string]interface{}{"name": filename, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_USAGE,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+
+        return "", whiskErr
+    }
+
+    file, err := ioutil.ReadFile(filename)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.ioutil.ReadFile(%s) error: %s\n", filename, err)
+        errMsg := wski18n.T("Unable to read '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": filename, "err": err})
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXITCODE_ERR_GENERAL,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+        return "", whiskErr
+    }
+
+    return string(file), nil
+}
+
+func fieldExists(value interface{}, field string) (bool) {
+    element := reflect.ValueOf(value).Elem()
+
+    for i := 0; i < element.NumField(); i++ {
+        if strings.ToLower(element.Type().Field(i).Name) == strings.ToLower(field) {
+            return true
+        }
+    }
+
+    return false
+}
+
+func printField(value interface{}, field string) {
+    var matchFunc = func(structField string) bool {
+        return strings.ToLower(structField) == strings.ToLower(field)
+    }
+
+    structValue := reflect.ValueOf(value)
+    fieldValue := reflect.Indirect(structValue).FieldByNameFunc(matchFunc)
+
+    printJSON(fieldValue.Interface())
+}
+
+func parseShared(shared string) (bool, bool, error) {
+    var isShared, isSet bool
+
+    if strings.ToLower(shared) == "yes" {
+        isShared = true
+        isSet = true
+    } else if strings.ToLower(shared) == "no" {
+        isShared = false
+        isSet = true
+    } else if len(shared) == 0 {
+        isSet = false
+    } else {
+        whisk.Debug(whisk.DbgError, "Cannot use value '%s' for shared.\n", shared)
+        errMsg := wski18n.T("Cannot use value '{{.arg}}' for shared.", map[string]interface{}{"arg": shared})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG,
+            whisk.DISPLAY_USAGE)
+        return false, false, whiskErr
+    }
+
+    whisk.Debug(whisk.DbgError, "Sharing is '%t'\n", isShared)
+
+    return isShared, isSet, nil
+}
+
+func max(a int, b int) int {
+    if (a > b) {
+        return a
+    }
+    return b
+}
+
+func min (a int, b int) int {
+    if (a < b) {
+        return a
+    }
+    return b
+}
+
+func readProps(path string) (map[string]string, error) {
+
+    props := map[string]string{}
+
+    file, err := os.Open(path)
+    if err != nil {
+        // If file does not exist, just return props
+        whisk.Debug(whisk.DbgWarn, "Unable to read whisk properties file '%s' (file open error: %s); falling back to default properties\n" ,path, err)
+        return props, nil
+    }
+    defer file.Close()
+
+    lines := []string{}
+    scanner := bufio.NewScanner(file)
+    for scanner.Scan() {
+        lines = append(lines, scanner.Text())
+    }
+
+    props = map[string]string{}
+    for _, line := range lines {
+        kv := strings.Split(line, "=")
+        if len(kv) != 2 {
+            // Invalid format; skip
+            continue
+        }
+        props[kv[0]] = kv[1]
+    }
+
+    return props, nil
+
+}
+
+func writeProps(path string, props map[string]string) error {
+
+    file, err := os.Create(path)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", path, err)
+        errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return werr
+    }
+    defer file.Close()
+
+    writer := bufio.NewWriter(file)
+    defer writer.Flush()
+    for key, value := range props {
+        line := fmt.Sprintf("%s=%s", strings.ToUpper(key), value)
+        _, err = fmt.Fprintln(writer, line)
+        if err != nil {
+            whisk.Debug(whisk.DbgError, "fmt.Fprintln() write to '%s' failed: %s\n", path, err)
+            errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
+            werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+            return werr
+        }
+    }
+    return nil
+}
+
+func getSpaceGuid() (string, error) {
+    // get current props
+    props, err := readProps(Properties.PropsFile)
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
+        errStr := wski18n.T("Unable to obtain the `auth` property value: {{.err}}", map[string]interface{}{"err": err})
+        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+        return "", werr
+    }
+
+    // get the auth key and parse out the space guid
+    if authToken, hasProp := props["AUTH"]; hasProp {
+        spaceGuid := strings.Split(authToken, ":")[0]
+        return spaceGuid, nil
+    }
+
+    whisk.Debug(whisk.DbgError, "auth not found in properties: %#q\n", props)
+    errStr := wski18n.T("Auth key property value is not set")
+    werr := whisk.MakeWskError(errors.New(errStr), whisk.EXITCODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
+    return "", werr
+}
+
+func isBlockingTimeout(err error) (bool) {
+    var blockingTimeout bool
+
+    whiskErr, isWhiskErr := err.(*whisk.WskError)
+
+    if isWhiskErr && whiskErr.TimedOut {
+        blockingTimeout = true
+    }
+
+    return blockingTimeout
+}
+
+func isApplicationError(err error) (bool) {
+    var applicationError bool
+
+    whiskErr, isWhiskErr := err.(*whisk.WskError)
+
+    if isWhiskErr && whiskErr.ApplicationError {
+        applicationError = true
+    }
+
+    return applicationError
+}
diff --git a/tools/cli/go-whisk-cli/commands/wsk.go b/tools/cli/go-whisk-cli/commands/wsk.go
new file mode 100644
index 0000000..7290549
--- /dev/null
+++ b/tools/cli/go-whisk-cli/commands/wsk.go
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package commands
+
+import (
+    "github.com/spf13/cobra"
+    "../wski18n"
+)
+
+// WskCmd defines the entry point for the cli.
+var WskCmd = &cobra.Command{
+    Use:              "wsk",
+    Short:            wski18n.T("OpenWhisk cloud computing command line interface."),
+    Long:             logoText(),
+    SilenceUsage:     true,
+    PersistentPreRunE:parseConfigFlags,
+}
+
+
+
+
+func init() {
+    WskCmd.SetHelpTemplate(`{{with or .Long .Short }}{{.}}
+{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`)
+
+    WskCmd.AddCommand(
+        actionCmd,
+        activationCmd,
+        packageCmd,
+        ruleCmd,
+        triggerCmd,
+        sdkCmd,
+        propertyCmd,
+        namespaceCmd,
+        listCmd,
+        apiExperimentalCmd,
+        apiCmd,
+    )
+
+    WskCmd.PersistentFlags().BoolVarP(&flags.global.verbose, "verbose", "v", false, wski18n.T("verbose output"))
+    WskCmd.PersistentFlags().BoolVarP(&flags.global.debug, "debug", "d", false, wski18n.T("debug level output"))
+    WskCmd.PersistentFlags().StringVarP(&flags.global.auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
+    WskCmd.PersistentFlags().StringVar(&flags.global.apihost, "apihost", "", wski18n.T("whisk API `HOST`"))
+    WskCmd.PersistentFlags().StringVar(&flags.global.apiversion, "apiversion", "", wski18n.T("whisk API `VERSION`"))
+    WskCmd.PersistentFlags().BoolVarP(&flags.global.insecure, "insecure", "i", false, wski18n.T("bypass certificate checking"))
+}
diff --git a/tools/cli/go-whisk-cli/main.go b/tools/cli/go-whisk-cli/main.go
new file mode 100644
index 0000000..a26f8fc
--- /dev/null
+++ b/tools/cli/go-whisk-cli/main.go
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package main
+
+import (
+    "fmt"
+    "os"
+    "reflect"
+    goi18n "github.com/nicksnyder/go-i18n/i18n"
+    "github.com/fatih/color"
+
+    "../go-whisk/whisk"
+    "./commands"
+    "./wski18n"
+    "github.com/mattn/go-colorable"
+)
+
+// CLI_BUILD_TIME holds the time of the CLI build.  During gradle builds,
+// this value will be overwritten via the command:
+//     go build -ldflags "-X main.CLI_BUILD_TIME=nnnnn"   // nnnnn is the new timestamp
+var CLI_BUILD_TIME string = "not set"
+
+var cliDebug = os.Getenv("WSK_CLI_DEBUG")  // Useful for tracing init() code
+
+var T goi18n.TranslateFunc
+
+func init() {
+    if len(cliDebug) > 0 {
+        whisk.SetDebug(true)
+    }
+
+    T = wski18n.T
+
+    // Rest of CLI uses the Properties struct, so set the build time there
+    commands.Properties.CLIVersion = CLI_BUILD_TIME
+}
+
+func main() {
+    var exitCode int = 0
+    var displayUsage bool = false
+    var displayMsg bool = false
+    var msgDisplayed bool = true
+    var displayPrefix bool = true
+
+    defer func() {
+        if r := recover(); r != nil {
+            fmt.Println(r)
+            fmt.Println(T("Application exited unexpectedly"))
+        }
+    }()
+
+    if err := commands.Execute(); err != nil {
+        whisk.Debug(whisk.DbgInfo, "err object type: %s\n", reflect.TypeOf(err).String())
+
+        werr, isWskError := err.(*whisk.WskError)  // Is the err a WskError?
+        if isWskError {
+            whisk.Debug(whisk.DbgError, "Got a *whisk.WskError error: %#v\n", werr)
+            displayUsage = werr.DisplayUsage
+            displayMsg = werr.DisplayMsg
+            msgDisplayed = werr.MsgDisplayed
+            displayPrefix = werr.DisplayPrefix
+            exitCode = werr.ExitCode
+        } else {
+            whisk.Debug(whisk.DbgError, "Got some other error: %s\n", err)
+            fmt.Fprintf(os.Stderr, "%s\n", err)
+
+            displayUsage = false   // Cobra already displayed the usage message
+            exitCode = 1;
+        }
+
+        outputStream := colorable.NewColorableStderr()
+
+        // If the err msg should be displayed to the console and it has not already been
+        // displayed, display it now.
+        if displayMsg && !msgDisplayed && displayPrefix && exitCode != 0 {
+            fmt.Fprintf(outputStream, "%s%s\n", color.RedString(T("error: ")), err)
+        } else if displayMsg && !msgDisplayed && !displayPrefix && exitCode != 0 {
+            fmt.Fprintf(outputStream, "%s\n", err)
+        } else if displayMsg && !msgDisplayed && exitCode == 0 {
+            fmt.Fprintf(outputStream, "%s\n", err)
+        }
+
+        // Displays usage
+        if displayUsage {
+            fmt.Fprintf(outputStream, T("Run '{{.Name}} --help' for usage.\n",
+                map[string]interface{}{ "Name" : commands.WskCmd.CommandPath()}))
+        }
+    }
+    os.Exit(exitCode)
+    return
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/detection.go b/tools/cli/go-whisk-cli/wski18n/detection.go
new file mode 100644
index 0000000..03d9be8
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/detection.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package wski18n
+
+import "github.com/cloudfoundry/jibber_jabber"
+
+type Detector interface {
+    DetectLocale() string
+    DetectLanguage() string
+}
+
+type JibberJabberDetector struct{}
+
+func (d *JibberJabberDetector) DetectLocale() string {
+    userLocale, err := jibber_jabber.DetectIETF()
+    if err != nil {
+        userLocale = ""
+    }
+    return userLocale
+}
+
+func (d *JibberJabberDetector) DetectLanguage() string {
+    lang, err := jibber_jabber.DetectLanguage()
+    if err != nil {
+        lang = ""
+    }
+    return lang
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/i18n.go b/tools/cli/go-whisk-cli/wski18n/i18n.go
new file mode 100644
index 0000000..3c68b5f
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/i18n.go
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package wski18n
+
+import (
+    "path/filepath"
+    "strings"
+
+    goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+const (
+    DEFAULT_LOCALE = "en_US"
+)
+
+var SUPPORTED_LOCALES = []string{
+    "de_DE",
+    "en_US",
+    "es_ES",
+    "fr_FR",
+    "it_IT",
+    "ja_JA",
+    "ko_KR",
+    "pt_BR",
+    "zh_Hans",
+    "zh_Hant",
+}
+
+var resourcePath = filepath.Join("wski18n", "resources")
+
+func GetResourcePath() string {
+    return resourcePath
+}
+
+func SetResourcePath(path string) {
+    resourcePath = path
+}
+
+var T goi18n.TranslateFunc
+var curLocale string
+
+func init() {
+    curLocale = Init(new(JibberJabberDetector))
+}
+
+func CurLocale() string {
+    return curLocale
+}
+
+func Locale(detector Detector) string {
+
+    // Use default locale until strings are translated
+    /*sysLocale := normalize(detector.DetectLocale())
+    if isSupported(sysLocale) {
+        return sysLocale
+    }
+
+    locale := defaultLocaleForLang(detector.DetectLanguage())
+    if locale != "" {
+        return locale
+    }*/
+
+    return DEFAULT_LOCALE
+}
+
+func Init(detector Detector) string {
+    l := Locale(detector)
+    InitWithLocale(l)
+    return l
+}
+
+func InitWithLocale(locale string) {
+    err := loadFromAsset(locale)
+    if err != nil {
+        panic(err)
+    }
+    T = goi18n.MustTfunc(locale)
+}
+
+func loadFromAsset(locale string) (err error) {
+    assetName := locale + ".all.json"
+    assetKey := filepath.Join(resourcePath, assetName)
+    bytes, err := Asset(assetKey)
+    if err != nil {
+        return
+    }
+    err = goi18n.ParseTranslationFileBytes(assetName, bytes)
+    return
+}
+
+func normalize(locale string) string {
+    locale = strings.ToLower(strings.Replace(locale, "-", "_", 1))
+    for _, l := range SUPPORTED_LOCALES {
+        if strings.EqualFold(locale, l) {
+            return l
+        }
+    }
+    switch locale {
+    case "zh_cn", "zh_sg":
+        return "zh_Hans"
+    case "zh_hk", "zh_tw":
+        return "zh_Hant"
+    }
+    return locale
+}
+
+func isSupported(locale string) bool {
+    for _, l := range SUPPORTED_LOCALES {
+        if strings.EqualFold(locale, l) {
+            return true
+        }
+    }
+    return false
+}
+
+func defaultLocaleForLang(lang string) string {
+    if lang != "" {
+        lang = strings.ToLower(lang)
+        for _, l := range SUPPORTED_LOCALES {
+            if lang == LangOfLocale(l) {
+                return l
+            }
+        }
+    }
+    return ""
+}
+
+func LangOfLocale(locale string) string {
+    if len(locale) < 2 {
+        return ""
+    }
+    return locale[0:2]
+}
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/de_DE.all.json b/tools/cli/go-whisk-cli/wski18n/resources/de_DE.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json b/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json
new file mode 100644
index 0000000..e173b42
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/resources/en_US.all.json
@@ -0,0 +1,1466 @@
+[
+  {
+    "id": "Application exited unexpectedly",
+    "translation": "Application exited unexpectedly"
+  },
+  {
+    "id": "Run '{{.Name}} --help' for usage.\n",
+    "translation": "Run '{{.Name}} --help' for usage.\n"
+  },
+  {
+    "id": "error: ",
+    "translation": "error: "
+  },
+  {
+    "id": "not set",
+    "translation": "not set"
+  },
+  {
+    "id": "OpenWhisk cloud computing command line interface.",
+    "translation": "OpenWhisk cloud computing command line interface."
+  },
+  {
+    "id": "verbose output",
+    "translation": "verbose output"
+  },
+  {
+    "id": "debug level output",
+    "translation": "debug level output"
+  },
+  {
+    "id": "authorization `KEY`",
+    "translation": "authorization `KEY`"
+  },
+  {
+    "id": "whisk API `HOST`",
+    "translation": "whisk API `HOST`"
+  },
+  {
+    "id": "whisk API `VERSION`",
+    "translation": "whisk API `VERSION`"
+  },
+  {
+    "id": "bypass certificate checking",
+    "translation": "bypass certificate checking"
+  },
+  {
+    "id": "Unable to initialize server connection: {{.err}}",
+    "translation": "Unable to initialize server connection: {{.err}}"
+  },
+  {
+    "id": "Parameter arguments must be a key value pair: {{.args}}",
+    "translation": "Parameter arguments must be a key value pair: {{.args}}"
+  },
+  {
+    "id": "Annotation arguments must be a key value pair: {{.args}}",
+    "translation": "Annotation arguments must be a key value pair: {{.args}}"
+  },
+  {
+    "id": "Failed to parse arguments: {{.err}}",
+    "translation": "Failed to parse arguments: {{.err}}"
+  },
+  {
+    "id": "work with namespaces",
+    "translation": "work with namespaces"
+  },
+  {
+    "id": "list entities in the current namespace",
+    "translation": "list entities in the current namespace"
+  },
+  {
+    "id": "Unable to obtain the list of available namespaces: {{.err}}",
+    "translation": "Unable to obtain the list of available namespaces: {{.err}}"
+  },
+  {
+    "id": "get triggers, actions, and rules in the registry for a namespace",
+    "translation": "get triggers, actions, and rules in the registry for a namespace"
+  },
+  {
+    "id": "'{{.name}}' is not a valid qualified name: {{.err}}",
+    "translation": "'{{.name}}' is not a valid qualified name: {{.err}}"
+  },
+  {
+    "id": "Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}",
+    "translation": "Unable to obtain the list of entities for namespace '{{.namespace}}': {{.err}}"
+  },
+  {
+    "id": "Entities in namespace: {{.namespace}}\n",
+    "translation": "Entities in namespace: {{.namespace}}\n"
+  },
+  {
+    "id": "list available namespaces",
+    "translation": "list available namespaces"
+  },
+  {
+    "id": "work with packages",
+    "translation": "work with packages"
+  },
+  {
+    "id": "bind parameters to a package",
+    "translation": "bind parameters to a package"
+  },
+  {
+    "id": "Invalid parameter argument '{{.param}}': {{.err}}",
+    "translation": "Invalid parameter argument '{{.param}}': {{.err}}"
+  },
+  {
+    "id": "Invalid annotation argument '{{.annotation}}': {{.err}}",
+    "translation": "Invalid annotation argument '{{.annotation}}': {{.err}}"
+  },
+  {
+    "id": "Binding creation failed: {{.err}}",
+    "translation": "Binding creation failed: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created binding {{.name}}\n",
+    "translation": "{{.ok}} created binding {{.name}}\n"
+  },
+  {
+    "id": "create a new package",
+    "translation": "create a new package"
+  },
+  {
+    "id": "Unable to create package '{{.name}}': {{.err}}",
+    "translation": "Unable to create package '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created package {{.name}}\n",
+    "translation": "{{.ok}} created package {{.name}}\n"
+  },
+  {
+    "id": "update an existing package, or create a package if it does not exist",
+    "translation": "update an existing package, or create a package if it does not exist"
+  },
+  {
+    "id": "Package update failed: {{.err}}",
+    "translation": "Package update failed: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} updated package {{.name}}\n",
+    "translation": "{{.ok}} updated package {{.name}}\n"
+  },
+  {
+    "id": "get package",
+    "translation": "get package"
+  },
+  {
+    "id": "Unable to get package '{{.name}}': {{.err}}",
+    "translation": "Unable to get package '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} got package {{.name}}\n",
+    "translation": "{{.ok}} got package {{.name}}\n"
+  },
+  {
+    "id": "delete package",
+    "translation": "delete package"
+  },
+  {
+    "id": "Unable to delete package '{{.name}}': {{.err}}",
+    "translation": "Unable to delete package '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} deleted package {{.name}}\n",
+    "translation": "{{.ok}} deleted package {{.name}}\n"
+  },
+  {
+    "id": "list all packages",
+    "translation": "list all packages"
+  },
+  {
+    "id": "No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\"",
+    "translation": "No valid namespace detected. Run 'wsk property set --namespace' or ensure the name argument is preceded by a \"/\""
+  },
+  {
+    "id": "Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}",
+    "translation": "Unable to obtain the list of packages for namespace '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "refresh package bindings",
+    "translation": "refresh package bindings"
+  },
+  {
+    "id": "Package refresh for namespace '{{.name}}' failed: {{.err}}",
+    "translation": "Package refresh for namespace '{{.name}}' failed: {{.err}}"
+  },
+  {
+    "id": "{{.name}} refreshed successfully\n",
+    "translation": "{{.name}} refreshed successfully\n"
+  },
+  {
+    "id": "created bindings:",
+    "translation": "created bindings:"
+  },
+  {
+    "id": "updated bindings:",
+    "translation": "updated bindings:"
+  },
+  {
+    "id": "deleted bindings:",
+    "translation": "deleted bindings:"
+  },
+  {
+    "id": "The package refresh feature is not implemented in the target deployment",
+    "translation": "The package refresh feature is not implemented in the target deployment"
+  },
+  {
+    "id": "Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}",
+    "translation": "Package refresh for namespace '{{.name}}' failed due to unexpected HTTP status code: {{.code}}"
+  },
+  {
+    "id": "annotation values in `KEY VALUE` format",
+    "translation": "annotation values in `KEY VALUE` format"
+  },
+  {
+    "id": "`FILE` containing annotation values in JSON format",
+    "translation": "`FILE` containing annotation values in JSON format"
+  },
+  {
+    "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'.  THOSE ARE FIXED CLI ARGUMENT VALUES",
+    "id": "package visibility `SCOPE`; yes = shared, no = private",
+    "translation": "package visibility `SCOPE`; yes = shared, no = private"
+  },
+  {
+    "id": "summarize package details",
+    "translation": "summarize package details"
+  },
+  {
+    "id": "include publicly shared entities in the result",
+    "translation": "include publicly shared entities in the result"
+  },
+  {
+    "id": "exclude the first `SKIP` number of packages from the result",
+    "translation": "exclude the first `SKIP` number of packages from the result"
+  },
+  {
+    "id": "only return `LIMIT` number of packages from the collection",
+    "translation": "only return `LIMIT` number of packages from the collection"
+  },
+  {
+    "id": "property",
+    "translation": "property"
+  },
+  {
+    "id": "work with whisk properties",
+    "translation": "work with whisk properties"
+  },
+  {
+    "id": "set property",
+    "translation": "set property"
+  },
+  {
+    "id": "Unable to set the property value: {{.err}}",
+    "translation": "Unable to set the property value: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n",
+    "translation": "{{.ok}} whisk auth set. Run 'wsk property get --auth' to see the new value.\n"
+  },
+  {
+    "id": "Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}",
+    "translation": "Unable to set API host value; the API host value '{{.apihost}}' is invalid: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} whisk API host set to {{.host}}\n",
+    "translation": "{{.ok}} whisk API host set to {{.host}}\n"
+  },
+  {
+    "id": "{{.ok}} whisk API version set to {{.version}}\n",
+    "translation": "{{.ok}} whisk API version set to {{.version}}\n"
+  },
+  {
+    "id": "Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}",
+    "translation": "Authenticated user does not have namespace '{{.name}}'; set command failed: {{.err}}"
+  },
+  {
+    "id": "Namespace '{{.name}}' is not in the list of entitled namespaces",
+    "translation": "Namespace '{{.name}}' is not in the list of entitled namespaces"
+  },
+  {
+    "id": "{{.ok}} whisk namespace set to {{.name}}\n",
+    "translation": "{{.ok}} whisk namespace set to {{.name}}\n"
+  },
+  {
+    "id": "Unable to set the property value(s): {{.err}}",
+    "translation": "Unable to set the property value(s): {{.err}}"
+  },
+  {
+    "id": "unset property",
+    "translation": "unset property"
+  },
+  {
+    "id": "Unable to unset the property value: {{.err}}",
+    "translation": "Unable to unset the property value: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} whisk auth unset.\n",
+    "translation": "{{.ok}} whisk auth unset.\n"
+  },
+  {
+    "id": "{{.ok}} whisk namespace unset",
+    "translation": "{{.ok}} whisk namespace unset"
+  },
+  {
+    "id": "{{.ok}} whisk API host unset.\n",
+    "translation": "{{.ok}} whisk API host unset.\n"
+  },
+  {
+    "id": "{{.ok}} whisk API version unset",
+    "translation": "{{.ok}} whisk API version unset"
+  },
+  {
+    "id": "; the default value of {{.default}} will be used.\n",
+    "translation": "; the default value of {{.default}} will be used.\n"
+  },
+  {
+    "id": "; there is no default value that can be used.\n",
+    "translation": "; there is no default value that can be used.\n"
+  },
+  {
+    "id": "get property",
+    "translation": "get property"
+  },
+  {
+    "id": "whisk auth",
+    "translation": "whisk auth"
+  },
+  {
+    "id": "whisk API host",
+    "translation": "whisk API host"
+  },
+  {
+    "id": "whisk API version",
+    "translation": "whisk API version"
+  },
+  {
+    "id": "whisk namespace",
+    "translation": "whisk namespace"
+  },
+  {
+    "id": "whisk CLI version",
+    "translation": "whisk CLI version"
+  },
+  {
+    "id": "Unknown",
+    "translation": "Unknown"
+  },
+  {
+    "id": "whisk API build",
+    "translation": "whisk API build"
+  },
+  {
+    "id": "whisk API build number",
+    "translation": "whisk API build number"
+  },
+  {
+    "id": "Unable to obtain API build information: {{.err}}",
+    "translation": "Unable to obtain API build information: {{.err}}"
+  },
+  {
+    "id": "authorization key",
+    "translation": "authorization key"
+  },
+  {
+    "id": "whisk API build version",
+    "translation": "whisk API build version"
+  },
+  {
+    "id": "all properties",
+    "translation": "all properties"
+  },
+  {
+    "id": "whisk `NAMESPACE`",
+    "translation": "whisk `NAMESPACE`"
+  },
+  {
+    "id": "Unable to locate properties file '{{.filename}}': {{.err}}",
+    "translation": "Unable to locate properties file '{{.filename}}': {{.err}}"
+  },
+  {
+    "id": "Unable to read the properties file '{{.filename}}': {{.err}}",
+    "translation": "Unable to read the properties file '{{.filename}}': {{.err}}"
+  },
+  {
+    "id": "Invalid host address '{{.host}}': {{.err}}",
+    "translation": "Invalid host address '{{.host}}': {{.err}}"
+  },
+  {
+    "id": "Whisk properties file write failure: {{.err}}",
+    "translation": "Whisk properties file write failure: {{.err}}"
+  },
+  {
+    "id": "work with rules",
+    "translation": "work with rules"
+  },
+  {
+    "id": "enable rule",
+    "translation": "enable rule"
+  },
+  {
+    "id": "Unable to enable rule '{{.name}}': {{.err}}",
+    "translation": "Unable to enable rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} enabled rule {{.name}}\n",
+    "translation": "{{.ok}} enabled rule {{.name}}\n"
+  },
+  {
+    "id": "disable rule",
+    "translation": "disable rule"
+  },
+  {
+    "id": "Unable to disable rule '{{.name}}': {{.err}}",
+    "translation": "Unable to disable rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} disabled rule {{.name}}\n",
+    "translation": "{{.ok}} disabled rule {{.name}}\n"
+  },
+  {
+    "id": "get rule status",
+    "translation": "get rule status"
+  },
+  {
+    "id": "Unable to get rule '{{.name}}': {{.err}}",
+    "translation": "Unable to get rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to get status of rule '{{.name}}': {{.err}}",
+    "translation": "Unable to get status of rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} rule {{.name}} is {{.status}}\n",
+    "translation": "{{.ok}} rule {{.name}} is {{.status}}\n"
+  },
+  {
+    "id": "create new rule",
+    "translation": "create new rule"
+  },
+  {
+    "id": "Unable to create rule '{{.name}}': {{.err}}",
+    "translation": "Unable to create rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created rule {{.name}}\n",
+    "translation": "{{.ok}} created rule {{.name}}\n"
+  },
+  {
+    "id": "update an existing rule, or create a rule if it does not exist",
+    "translation": "update an existing rule, or create a rule if it does not exist"
+  },
+  {
+    "id": "Unable to update rule '{{.name}}': {{.err}}",
+    "translation": "Unable to update rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} updated rule {{.name}}\n",
+    "translation": "{{.ok}} updated rule {{.name}}\n"
+  },
+  {
+    "id": "get rule",
+    "translation": "get rule"
+  },
+  {
+    "id": "Unable to retrieve rule '{{.name}}': {{.err}}",
+    "translation": "Unable to retrieve rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} got rule {{.name}}\n",
+    "translation": "{{.ok}} got rule {{.name}}\n"
+  },
+  {
+    "id": "delete rule",
+    "translation": "delete rule"
+  },
+  {
+    "id": "Unable to delete rule '{{.name}}': {{.err}}",
+    "translation": "Unable to delete rule '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} deleted rule {{.name}}\n",
+    "translation": "{{.ok}} deleted rule {{.name}}\n"
+  },
+  {
+    "id": "list all rules",
+    "translation": "list all rules"
+  },
+  {
+    "id": "Namespace '{{.name}}' is invalid: {{.err}}\n",
+    "translation": "Namespace '{{.name}}' is invalid: {{.err}}\n"
+  },
+  {
+    "id": "Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}",
+    "translation": "Unable to obtain the list of rules for namespace '{{.name}}': {{.err}}"
+  },
+  {
+    "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'.  THOSE ARE FIXED CLI ARGUMENT VALUES",
+    "id": "rule visibility `SCOPE`; yes = shared, no = private",
+    "translation": "rule visibility `SCOPE`; yes = shared, no = private"
+  },
+  {
+    "id": "automatically enable rule after creating it",
+    "translation": "automatically enable rule after creating it"
+  },
+  {
+    "id": "automatically disable rule before deleting it",
+    "translation": "automatically disable rule before deleting it"
+  },
+  {
+    "id": "summarize rule details",
+    "translation": "summarize rule details"
+  },
+  {
+    "id": "work with the sdk",
+    "translation": "work with the sdk"
+  },
+  {
+    "id": "install SDK artifacts",
+    "translation": "install SDK artifacts"
+  },
+  {
+    "id": "install SDK artifacts, where valid COMPONENT values are docker, ios, and bashauto",
+    "translation": "install SDK artifacts, where valid COMPONENT values are docker, iOS, and bashauto"
+  },
+  {
+    "id": "The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified",
+    "translation": "The SDK component argument is missing. One component (docker, ios, or bashauto) must be specified"
+  },
+  {
+    "id": "Unable to generate '{{.name}}': {{.err}}",
+    "translation": "Unable to generate '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "bash_completion_msg",
+    "translation": "The bash auto-completion script ({{.name}}) is installed in the current directory.\nTo enable command line completion of wsk commands, source the auto completion script into your bash environment\n"
+  },
+  {
+    "id": "The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto",
+    "translation": "The SDK component argument '{{.component}}' is invalid. Valid components are docker, ios and bashauto"
+  },
+  {
+    "id": "The file {{.name}} already exists.  Delete it and retry.",
+    "translation": "The file {{.name}} already exists.  Delete it and retry."
+  },
+  {
+    "id": "The {{.component}} SDK installation failed: {{.err}}",
+    "translation": "The {{.component}} SDK installation failed: {{.err}}"
+  },
+  {
+    "id": "The docker skeleton is now installed at the current directory.",
+    "translation": "The docker skeleton is now installed at the current directory."
+  },
+  {
+    "id": "Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n",
+    "translation": "Downloaded OpenWhisk iOS starter app. Unzip {{.name}} and open the project in Xcode.\n"
+  },
+  {
+    "id": "Unable to retrieve '{{.urlpath}}' SDK: {{.err}}",
+    "translation": "Unable to retrieve '{{.urlpath}}' SDK: {{.err}}"
+  },
+  {
+    "id": "Server failed to send the '{{.component}}' SDK: {{.err}}",
+    "translation": "Server failed to send the '{{.component}}' SDK: {{.err}}"
+  },
+  {
+    "id": "Error creating SDK file {{.name}}: {{.err}}",
+    "translation": "Error creating SDK file {{.name}}: {{.err}}"
+  },
+  {
+    "id": "Error copying server response into file: {{.err}}",
+    "translation": "Error copying server response into file: {{.err}}"
+  },
+  {
+    "id": "The directory {{.name}} already exists.  Delete it and retry.",
+    "translation": "The directory {{.name}} already exists.  Delete it and retry."
+  },
+  {
+    "id": "Error unGzipping file {{.name}}: {{.err}}",
+    "translation": "Error unGzipping file {{.name}}: {{.err}}"
+  },
+  {
+    "id": "Error untarring file {{.name}}: {{.err}}",
+    "translation": "Error untarring file {{.name}}: {{.err}}"
+  },
+  {
+    "id": "work with triggers",
+    "translation": "work with triggers"
+  },
+  {
+    "id": "fire trigger event",
+    "translation": "fire trigger event"
+  },
+  {
+    "id": "Unable to fire trigger '{{.name}}': {{.err}}",
+    "translation": "Unable to fire trigger '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n",
+    "translation": "{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n"
+  },
+  {
+    "id": "create new trigger",
+    "translation": "create new trigger"
+  },
+  {
+    "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'.  THOSE ARE FIXED CLI ARGUMENT VALUES",
+    "id": "Invalid --shared argument value '{{.argval}}'; valid values are 'yes' or 'no'",
+    "translation": "Invalid --shared argument value '{{.argval}}'; valid values are 'yes' or 'no'"
+  },
+  {
+    "id": "Unable to create trigger '{{.name}}': {{.err}}",
+    "translation": "Unable to create trigger '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created trigger {{.name}}\n",
+    "translation": "{{.ok}} created trigger {{.name}}\n"
+  },
+  {
+    "id": "update an existing trigger, or create a trigger if it does not exist",
+    "translation": "update an existing an trigger, or create a trigger if it does not exist"
+  },
+  {
+    "id": "Unable to update trigger '{{.name}}': {{.err}}",
+    "translation": "Unable to update trigger '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} updated trigger {{.name}}\n",
+    "translation": "{{.ok}} updated trigger {{.name}}\n"
+  },
+  {
+    "id": "get trigger",
+    "translation": "get trigger"
+  },
+  {
+    "id": "Unable to get trigger '{{.name}}': {{.err}}",
+    "translation": "Unable to get trigger '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} got trigger {{.name}}\n",
+    "translation": "{{.ok}} got trigger {{.name}}\n"
+  },
+  {
+    "id": "delete trigger",
+    "translation": "delete trigger"
+  },
+  {
+    "id": "Unable to delete trigger '{{.name}}': {{.err}}",
+    "translation": "Unable to delete trigger '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} deleted trigger {{.name}}\n",
+    "translation": "{{.ok}} deleted trigger {{.name}}\n"
+  },
+  {
+    "id": "list all triggers",
+    "translation": "list all triggers"
+  },
+  {
+    "id": "Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}",
+    "translation": "Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}",
+    "translation": "Unable to invoke trigger '{{.trigname}}' feed action '{{.feedname}}'; feed is not configured: {{.err}}"
+  },
+  {
+    "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'.  THOSE ARE FIXED CLI ARGUMENT VALUES",
+    "id": "trigger visibility `SCOPE`; yes = shared, no = private",
+    "translation": "trigger visibility `SCOPE`; yes = shared, no = private"
+  },
+  {
+    "id": "trigger feed `ACTION_NAME`",
+    "translation": "trigger feed `ACTION_NAME`"
+  },
+  {
+    "id": "summarize trigger details",
+    "translation": "summarize trigger details"
+  },
+  {
+    "id": "parameter values in `KEY VALUE` format",
+    "translation": "parameter values in `KEY VALUE` format"
+  },
+  {
+    "id": "`FILE` containing parameter values in JSON format",
+    "translation": "`FILE` containing parameter values in JSON format"
+  },
+  {
+    "id": "Arguments must be comma separated, and must be quoted if they contain spaces.",
+    "translation": "Arguments must be comma separated, and must be quoted if they contain spaces."
+  },
+  {
+    "id": "Invalid arguments: {{.err}}",
+    "translation": "Invalid arguments: {{.err}}"
+  },
+  {
+    "id": "The argument `{{.arg}}` is invalid: {{.err}}",
+    "translation": "The argument `{{.arg}}` is invalid: {{.err}}"
+  },
+  {
+    "id": "The argument `{{.arg}}` is invalid JSON: {{.err}}",
+    "translation": "The argument `{{.arg}}` is invalid JSON: {{.err}}"
+  },
+  {
+    "id": "private",
+    "translation": "private"
+  },
+  {
+    "id": "shared",
+    "translation": "shared"
+  },
+  {
+    "id": "The file '{{.name}}' does not exist.",
+    "translation": "The file '{{.name}}' does not exist."
+  },
+  {
+    "id": "Error creating unGzip file '{{.name}}': {{.err}}",
+    "translation": "Error creating unGzip file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Error opening Gzip file '{{.name}}': {{.err}}",
+    "translation": "Error opening Gzip file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to unzip file '{{.name}}': {{.err}}",
+    "translation": "Unable to unzip file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to opens '{{.name}}' for unzipping: {{.err}}",
+    "translation": "Unable to opens '{{.name}}' for unzipping: {{.err}}"
+  },
+  {
+    "id": "Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
+    "translation": "Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+    "translation": "Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
+    "translation": "Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Error opening tar file '{{.name}}': {{.err}}",
+    "translation": "Error opening tar file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Error reading tar file '{{.name}}': {{.err}}",
+    "translation": "Error reading tar file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
+    "translation": "Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
+    "translation": "Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to untar file '{{.name}}': {{.err}}",
+    "translation": "Unable to untar file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Unable to untar '{{.name}}' due to unexpected tar file type\n",
+    "translation": "Unable to untar '{{.name}}' due to unexpected tar file type\n"
+  },
+  {
+    "id": "work with actions",
+    "translation": "work with actions"
+  },
+  {
+    "id": "create a new action",
+    "translation": "create a new action"
+  },
+  {
+    "id": "Unable to parse action command arguments: {{.err}}",
+    "translation": "Unable to parse action command arguments: {{.err}}"
+  },
+  {
+    "id": "Unable to create action '{{.name}}': {{.err}}",
+    "translation": "Unable to create action '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created action {{.name}}\n",
+    "translation": "{{.ok}} created action {{.name}}\n"
+  },
+  {
+    "id": "update an existing action, or create an action if it does not exist",
+    "translation": "update an existing action, or create an action if it does not exist"
+  },
+  {
+    "id": "Unable to update action: {{.err}}",
+    "translation": "Unable to update action: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} updated action {{.name}}\n",
+    "translation": "{{.ok}} updated action {{.name}}\n"
+  },
+  {
+    "id": "invoke action",
+    "translation": "invoke action"
+  },
+  {
+    "id": "Unable to invoke action '{{.name}}': {{.err}}",
+    "translation": "Unable to invoke action '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n",
+    "translation": "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n"
+  },
+  {
+    "id": "get action",
+    "translation": "get action"
+  },
+  {
+    "id": "Unable to get action: {{.err}}",
+    "translation": "Unable to get action: {{.err}}"
+  },
+  {
+    "id": "action",
+    "translation": "action"
+  },
+  {
+    "id": "{{.ok}} got action {{.name}}\n",
+    "translation": "{{.ok}} got action {{.name}}\n"
+  },
+  {
+    "id": "delete action",
+    "translation": "delete action"
+  },
+  {
+    "id": "Unable to delete action '{{.name}}': {{.err}}",
+    "translation": "Unable to delete action '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "{{.ok}} deleted action {{.name}}\n",
+    "translation": "{{.ok}} deleted action {{.name}}\n"
+  },
+  {
+    "id": "list all actions",
+    "translation": "list all actions"
+  },
+  {
+    "id": "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}",
+    "translation": "Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Could not find 'main' method in '{{.name}}'",
+    "translation": "Could not find 'main' method in '{{.name}}'"
+  },
+  {
+    "id": "Unable to get action '{{.name}}': {{.err}}",
+    "translation": "Unable to get action '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
+    "translation": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}"
+  },
+  {
+    "id": "Unable to read '{{.name}}': {{.err}}",
+    "translation": "Unable to read '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "'{{.name}}' is not a supported action runtime",
+    "translation": "'{{.name}}' is not a supported action runtime"
+  },
+  {
+    "id": "creating an action from a .zip artifact requires specifying the action kind explicitly",
+    "translation": "creating an action from a .zip artifact requires specifying the action kind explicitly"
+  },
+  {
+    "id": "Java actions require --main to specify the fully-qualified name of the main class",
+    "translation": "Java actions require --main to specify the fully-qualified name of the main class"
+  },
+  {
+    "id": "treat ACTION as docker image path on dockerhub",
+    "translation": "treat ACTION as docker image path on dockerhub"
+  },
+  {
+    "id": "treat ACTION as the name of an existing action",
+    "translation": "treat ACTION as the name of an existing action"
+  },
+  {
+    "id": "treat ACTION as comma separated sequence of actions to invoke",
+    "translation": "treat ACTION as comma separated sequence of actions to invoke"
+  },
+  {
+    "id": "the `KIND` of the action runtime (example: swift:default, nodejs:default)",
+    "translation": "the `KIND` of the action runtime (example: swift:default, nodejs:default)"
+  },
+  {
+    "id": "the name of the action entry point (function or fully-qualified method name when applicable)",
+    "translation": "the name of the action entry point (function or fully-qualified method name when applicable)"
+  },
+  {
+    "id": "action visibility `SCOPE`; yes = shared, no = private",
+    "translation": "action visibility `SCOPE`; yes = shared, no = private"
+  },
+  {
+    "id": "the timeout `LIMIT` in milliseconds after which the action is terminated",
+    "translation": "the timeout `LIMIT` in milliseconds after which the action is terminated"
+  },
+  {
+    "id": "the maximum memory `LIMIT` in MB for the action",
+    "translation": "the maximum memory `LIMIT` in MB for the action"
+  },
+  {
+    "id": "the maximum log size `LIMIT` in MB for the action",
+    "translation": "the maximum log size `LIMIT` in MB for the action"
+  },
+  {
+    "id": "blocking invoke",
+    "translation": "blocking invoke"
+  },
+  {
+    "id": "blocking invoke; show only activation result (unless there is a failure)",
+    "translation": "blocking invoke; show only activation result (unless there is a failure)"
+  },
+  {
+    "id": "exclude the first `SKIP` number of actions from the result",
+    "translation": "exclude the first `SKIP` number of actions from the result"
+  },
+  {
+    "id": "only return `LIMIT` number of actions from the collection",
+    "translation": "only return `LIMIT` number of actions from the collection"
+  },
+  {
+    "id": "summarize action details",
+    "translation": "summarize action details"
+  },
+  {
+    "id": "work with activations",
+    "translation": "work with activations"
+  },
+  {
+    "id": "list activations",
+    "translation": "list activations"
+  },
+  {
+    "id": "Namespace '{{.name}}' is invalid",
+    "translation": "Namespace '{{.name}}' is invalid"
+  },
+  {
+    "id": "Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}",
+    "translation": "Unable to obtain the list of activations for namespace '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "get activation",
+    "translation": "get activation"
+  },
+  {
+    "id": "Unable to get activation '{{.id}}': {{.err}}",
+    "translation": "Unable to get activation '{{.id}}': {{.err}}"
+  },
+  {
+    "id": "activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n",
+    "translation": "activation result for /{{.namespace}}/{{.name}} ({{.status}} at {{.time}})\n"
+  },
+  {
+    "id": "{{.ok}} got activation {{.id}}\n",
+    "translation": "{{.ok}} got activation {{.id}}\n"
+  },
+  {
+    "id": "get the logs of an activation",
+    "translation": "get the logs of an activation"
+  },
+  {
+    "id": "Unable to get logs for activation '{{.id}}': {{.err}}",
+    "translation": "Unable to get logs for activation '{{.id}}': {{.err}}"
+  },
+  {
+    "id": "Unable to get result for activation '{{.id}}': {{.err}}",
+    "translation": "Unable to get result for activation '{{.id}}': {{.err}}"
+  },
+  {
+    "id": "poll continuously for log messages from currently running actions",
+    "translation": "poll continuously for log messages from currently running actions"
+  },
+  {
+    "id": "Poll terminated",
+    "translation": "Poll terminated"
+  },
+  {
+    "id": "Enter Ctrl-c to exit.",
+    "translation": "Enter Ctrl-c to exit."
+  },
+  {
+    "id": "Polling for activation logs\n",
+    "translation": "Polling for activation logs\n"
+  },
+  {
+    "id": "\nActivation: {{.name}} ({{.id}})\n",
+    "translation": "\nActivation: {{.name}} ({{.id}})\n"
+  },
+  {
+    "id": "exclude the first `SKIP` number of activations from the result",
+    "translation": "exclude the first `SKIP` number of activations from the result"
+  },
+  {
+    "id": "only return `LIMIT` number of activations from the collection",
+    "translation": "only return `LIMIT` number of activations from the collection"
+  },
+  {
+    "id": "include full activation description",
+    "translation": "include full activation description"
+  },
+  {
+    "id": "return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970",
+    "translation": "return activations with timestamps earlier than `UPTO`; measured in milliseconds since Th, 01, Jan 1970"
+  },
+  {
+    "id": "return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970",
+    "translation": "return activations with timestamps later than `SINCE`; measured in milliseconds since Th, 01, Jan 1970"
+  },
+  {
+    "id": "summarize activation details",
+    "translation": "summarize activation details"
+  },
+  {
+    "id": "stop polling after `SECONDS` seconds",
+    "translation": "stop polling after `SECONDS` seconds"
+  },
+  {
+    "id": "start polling for activations `SECONDS` seconds ago",
+    "translation": "start polling for activations `SECONDS` seconds ago"
+  },
+  {
+    "id": "start polling for activations `MINUTES` minutes ago",
+    "translation": "start polling for activations `MINUTES` minutes ago"
+  },
+  {
+    "id": "start polling for activations `HOURS` hours ago",
+    "translation": "start polling for activations `HOURS` hours ago"
+  },
+  {
+    "id": "start polling for activations `DAYS` days ago",
+    "translation": "start polling for activations `DAYS` days ago"
+  },
+  {
+    "id": "Arguments for '{{.arg}}' must be a key/value pair",
+    "translation": "Arguments for '{{.arg}}' must be a key/value pair"
+  },
+  {
+    "id": "The parameter arguments are invalid: {{.err}}",
+    "translation": "The parameter arguments are invalid: {{.err}}"
+  },
+  {
+    "id": "The annotation arguments are invalid: {{.err}}",
+    "translation": "The annotation arguments are invalid: {{.err}}"
+  },
+  {
+    "id": "An action name and action are required.",
+    "translation": "An action name and action are required."
+  },
+  {
+    "id": "An action name is required.",
+    "translation": "An action name is required."
+  },
+  {
+    "id": "An action name is required. An action is optional.",
+    "translation": "An action name is required. An action is optional."
+  },
+  {
+    "id": "An activation ID is required.",
+    "translation": "An activation ID is required."
+  },
+  {
+    "id": "A package name and binding name are required.",
+    "translation": "A package name and binding name are required."
+  },
+  {
+    "id": "A package name is required.",
+    "translation": "A package name is required."
+  },
+  {
+    "id": "A rule name is required.",
+    "translation": "A rule name is required."
+  },
+  {
+    "id": "A rule, trigger and action name are required.",
+    "translation": "A rule, trigger and action name are required."
+  },
+  {
+    "id": "A trigger name is required.",
+    "translation": "A trigger name is required."
+  },
+  {
+    "id": "A trigger name is required. A payload is optional.",
+    "translation": "A trigger name is required. A payload is optional."
+  },
+  {
+    "id": "An optional namespace is the only valid argument.",
+    "translation": "An optional namespace is the only valid argument."
+  },
+  {
+    "id": "Invalid argument(s). {{.required}}",
+    "translation": "Invalid argument(s). {{.required}}"
+  },
+  {
+    "id": "Invalid argument(s): {{.args}}. {{.required}}",
+    "translation": "Invalid argument(s): {{.args}}. {{.required}}"
+  },
+  {
+    "id": "exactly",
+    "translation": "exactly"
+  },
+  {
+    "id": "at least",
+    "translation": "at least"
+  },
+  {
+    "id": "no more than",
+    "translation": "no more than"
+  },
+  {
+    "id": "No arguments are required.",
+    "translation": "No arguments are required."
+  },
+  {
+    "id": "status",
+    "translation": "status"
+  },
+  {
+    "id": "parameters",
+    "translation": "parameters"
+  },
+  {
+    "id": "default",
+    "translation": "default"
+  },
+  {
+    "id": "An argument must be provided for '{{.arg}}'",
+    "translation": "An argument must be provided for '{{.arg}}'"
+  },
+  {
+    "id": "The API host is not valid: {{.err}}",
+    "translation": "The API host is not valid: {{.err}}"
+  },
+  {
+    "id": "An API host must be provided.",
+    "translation": "An API host must be provided."
+  },
+  {
+    "id": "Invalid field filter '{{.arg}}'.",
+    "translation": "Invalid field filter '{{.arg}}'."
+  },
+  {
+    "id": "{{.ok}} got activation {{.id}}, displaying field {{.field}}\n",
+    "translation": "{{.ok}} got activation {{.id}}, displaying field {{.field}}\n"
+  },
+  {
+    "id": "{{.ok}} got action {{.name}}, displaying field {{.field}}\n",
+    "translation": "{{.ok}} got action {{.name}}, displaying field {{.field}}\n"
+  },
+  {
+    "id": "{{.ok}} got package {{.name}}, displaying field {{.field}}\n",
+    "translation": "{{.ok}} got package {{.name}}, displaying field {{.field}}\n"
+  },
+  {
+    "id": "{{.ok}} got rule {{.name}}, displaying field {{.field}}\n",
+    "translation": "{{.ok}} got rule {{.name}}, displaying field {{.field}}\n"
+  },
+  {
+    "id": "{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
+    "translation": "{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n"
+  },
+  {
+    "id": "An API path, an API verb, and an action name are required.",
+    "translation": "An API path, an API verb, and an action name are required."
+  },
+  {
+    "id": "An API path and an API verb are required.",
+    "translation": "An API path and an API verb are required."
+  },
+  {
+    "id":"'{{.name}}' is not a valid action name: {{.err}}",
+    "translation": "'{{.name}}' is not a valid action name: {{.err}}"
+  },
+  {
+    "id":"'{{.name}}' is not a valid action name.",
+    "translation": "'{{.name}}' is not a valid action name."
+  },
+  {
+    "id": "Unable to create API: {{.err}}",
+    "translation": "Unable to create API: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+    "translation": "{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n"
+  },
+  {
+    "id": "Unable to parse api command arguments: {{.err}}",
+    "translation": "Unable to parse api command arguments: {{.err}}"
+  },
+  {
+    "id": "create a new API",
+    "translation": "create a new API"
+  },
+  {
+    "id": "update an existing API",
+    "translation": "update an existing API"
+  },
+  {
+    "id": "Unable to update API: {{.err}}",
+    "translation": "Unable to update API: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
+    "translation": "{{.ok}} updated API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n"
+  },
+  {
+    "id": "get API",
+    "translation": "get API"
+  },
+  {
+    "id": "Unable to get API '{{.name}}': {{.err}}",
+    "translation": "Unable to get API '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "delete an API",
+    "translation": "delete an API"
+  },
+  {
+    "id": "Unable to delete API: {{.err}}",
+    "translation": "Unable to delete API: {{.err}}"
+  },
+  {
+    "id": "{{.ok}} deleted API {{.basepath}}\n",
+    "translation": "{{.ok}} deleted API {{.basepath}}\n"
+  },
+  {
+    "id": "{{.ok}} deleted {{.path}} from {{.basepath}}\n",
+    "translation": "{{.ok}} deleted {{.path}} from {{.basepath}}\n"
+  },
+  {
+    "id": "{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
+    "translation": "{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n"
+  },
+  {
+    "id": "list APIs",
+    "translation": "list APIs"
+  },
+  {
+    "id": "Unable to obtain the API list: {{.err}}",
+    "translation": "Unable to obtain the API list: {{.err}}"
+  },
+  {
+    "id": "'{{.verb}}' is not a valid API verb.  Valid values are: {{.verbs}}",
+    "translation": "'{{.verb}}' is not a valid API verb.  Valid values are: {{.verbs}}"
+  },
+  {
+    "id": "`ACTION` to invoke when API is called",
+    "translation": "`ACTION` to invoke when API is called"
+  },
+  {
+    "id": "relative `API_PATH` of API",
+    "translation": "relative `API_PATH` of API"
+  },
+  {
+    "id": "API `API_VERB`",
+    "translation": "API `API_VERB`"
+  },
+  {
+    "id": "API collection `NAME` (default NAMESPACE)",
+    "translation": "API collection `NAME` (default NAMESPACE)"
+  },
+  {
+    "id": "The API `BASE_PATH` to which the API_PATH is relative",
+    "translation": "The API `BASE_PATH` to which the API_PATH is relative"
+  },
+  {
+    "id": "{{.ok}} APIs\n",
+    "translation": "{{.ok}} APIs\n"
+  },
+  {
+    "id": "{{.url}} {{.operation}} {{.action}}\n",
+    "translation": "{{.url}} {{.operation}} {{.action}}\n"
+  },
+  {
+    "id": "An API base path is required.  An optional API relative path and operation may also be provided.",
+    "translation": "An API base path is required.  An optional API relative path and operation may also be provided."
+  },
+  {
+    "id": "'{{.path}}' must begin with '/'.",
+    "translation": "'{{.path}}' must begin with '/'."
+  },
+  {
+    "id": "Unable to parse swagger file: {{.err}}",
+    "translation": "Unable to parse swagger file: {{.err}}"
+  },
+  {
+    "id": "Error reading swagger file '{{.name}}': {{.err}}",
+    "translation": "Error reading swagger file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "A configuration file was not specified.",
+    "translation": "A configuration file was not specified."
+  },
+  {
+    "id": "Error parsing swagger file '{{.name}}': {{.err}}",
+    "translation": "Error parsing swagger file '{{.name}}': {{.err}}"
+  },
+  {
+    "id": "Swagger file is invalid (missing basePath, info, paths, or swagger fields)",
+    "translation": "Swagger file is invalid (missing basePath, info, paths, or swagger fields)"
+  },
+  {
+    "id": "Swagger file basePath must start with a leading slash (/)",
+    "translation": "Swagger file basePath must start with a leading slash (/)"
+  },
+  {
+    "id": "API does not exist for basepath {{.basepath}}",
+    "translation": "API does not exist for basepath {{.basepath}}"
+  },
+  {
+    "id": "API does not exist for API name {{.apiname}}",
+    "translation": "API does not exist for API name {{.apiname}}"
+  },
+  {
+    "id": "An API name can only be specified once",
+    "translation": "An API name can only be specified once"
+  },
+  {
+    "id": "Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.",
+    "translation": "Specify a swagger file or specify an API base path with an API path, an API verb, and an action name."
+  },
+  {
+    "id": "Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.",
+    "translation": "Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name."
+  },
+  {
+    "id": "Cannot use value '{{.arg}}' for shared.",
+    "translation": "Cannot use value '{{.arg}}' for shared."
+  },
+  {
+    "id": "Action",
+    "translation": "Action"
+  },
+  {
+    "id": "Verb",
+    "translation": "Verb"
+  },
+  {
+    "id": "API Name",
+    "translation": "API Name"
+  },
+  {
+    "id": "URL",
+    "translation": "URL"
+  },
+  {
+    "id": "Base path",
+    "translation": "Base path"
+  },
+  {
+    "id": "Path",
+    "translation": "Path"
+  },
+  {
+    "id": "display full description of each API",
+    "translation": "display full description of each API"
+  },
+  {
+    "id": "An API host must be provided.",
+    "translation": "An API host must be provided."
+  },
+  {
+    "id": "Request accepted, but processing not completed yet.",
+    "translation": "Request accepted, but processing not completed yet."
+  },
+  {
+    "id": "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n",
+    "translation": "{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n"
+  },
+  {
+    "id": "treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action",
+    "translation": "treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"
+  },
+  {
+    "id": "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'.",
+    "translation": "Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'."
+  },
+  {
+    "id": "API action '{{.name}}' does not exist",
+    "translation": "API action '{{.name}}' does not exist"
+  },
+  {
+    "id": "API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action.",
+    "translation": "API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action."
+  },
+  {
+    "id": "Invalid configuration. The x-openwhisk stanza is missing.",
+    "translation": "Invalid configuration. The x-openwhisk stanza is missing."
+  },
+  {
+    "id": "Internal error. Missing value stanza in API configuration response",
+    "translation": "Internal error. Missing value stanza in API configuration response"
+  },
+  {
+    "id": "Internal error. Missing apidoc stanza in API configuration",
+    "translation": "Internal error. Missing apidoc stanza in API configuration"
+  },
+  {
+    "id": "Missing operationId field in API configuration for operation {{.op}}",
+    "translation": "Missing operationId field in API configuration for operation {{.op}}"
+  },
+  {
+    "id": "Missing x-openwhisk stanza in API configuration for operation {{.op}}",
+    "translation": "Missing x-openwhisk stanza in API configuration for operation {{.op}}"
+  },
+  {
+    "id": "Missing x-openwhisk.namespace field in API configuration for operation {{.op}}",
+    "translation": "Missing x-openwhisk.namespace field in API configuration for operation {{.op}}"
+  },
+  {
+    "id": "Missing x-openwhisk.action field in API configuration for operation {{.op}}",
+    "translation": "Missing x-openwhisk.action field in API configuration for operation {{.op}}"
+  },
+  {
+    "id": "Missing x-openwhisk.url field in API configuration for operation {{.op}}",
+    "translation": "Missing x-openwhisk.url field in API configuration for operation {{.op}}"
+  },
+  {
+    "id": "work with APIs",
+    "translation": "work with APIs"
+  },
+  {
+    "id": "create a new API",
+    "translation": "create a new API"
+  },
+  {
+    "id": "get API details",
+    "translation": "get API details"
+  },
+  {
+    "id": "delete an API",
+    "translation": "delete an API"
+  },
+  {
+    "id": "list APIs",
+    "translation": "list APIs"
+  },
+  {
+    "id": "Set the web action response TYPE. Possible values are html, http, json, text, svg",
+    "translation": "Set the web action response TYPE. Possible values are html, http, json, text, svg"
+  },
+  {
+    "id": "Authorization key is not configured (--auth is required)",
+    "translation": "Authorization key is not configured (--auth is required)"
+  },
+  {
+    "id": "Specify the API output `TYPE`, either json or yaml",
+    "translation": "Specify the API output `TYPE`, either json or yaml"
+  },
+  {
+    "id": "Unable to parse YAML configuration file: {{.err}}",
+    "translation": "Unable to parse YAML configuration file: {{.err}}"
+  },
+  {
+    "id": "Unable to convert API into YAML: {{.err}}",
+    "translation": "Unable to convert API into YAML: {{.err}}"
+  },
+  {
+    "id": "Invalid format type: {{.type}}",
+    "translation": "Invalid format type: {{.type}}"
+  }
+]
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/es_ES.all.json b/tools/cli/go-whisk-cli/wski18n/resources/es_ES.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json
new file mode 100644
index 0000000..55e81ef
--- /dev/null
+++ b/tools/cli/go-whisk-cli/wski18n/resources/fr_FR.all.json
@@ -0,0 +1,6 @@
+[
+  {
+    "id": "bypass certificate checking",
+    "translation": "Some translation in French"
+  }
+]
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/it_IT.all.json b/tools/cli/go-whisk-cli/wski18n/resources/it_IT.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/ja_JA.all.json b/tools/cli/go-whisk-cli/wski18n/resources/ja_JA.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/ko_KR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/ko_KR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/pt_BR.all.json b/tools/cli/go-whisk-cli/wski18n/resources/pt_BR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/zh_Hans.all.json b/tools/cli/go-whisk-cli/wski18n/resources/zh_Hans.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk-cli/wski18n/resources/zh_Hant.all.json b/tools/cli/go-whisk-cli/wski18n/resources/zh_Hant.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/whisk/action.go b/tools/cli/go-whisk/whisk/action.go
new file mode 100644
index 0000000..f2ced21
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/action.go
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "net/http"
+    "errors"
+    "net/url"
+    "../wski18n"
+)
+
+type ActionService struct {
+    client *Client
+}
+
+type Action struct {
+    Namespace   string      `json:"namespace,omitempty"`
+    Name        string      `json:"name,omitempty"`
+    Version     string      `json:"version,omitempty"`
+    Exec        *Exec       `json:"exec,omitempty"`
+    Annotations KeyValueArr `json:"annotations,omitempty"`
+    Parameters  KeyValueArr `json:"parameters,omitempty"`
+    Limits      *Limits     `json:"limits,omitempty"`
+    Error       string      `json:"error,omitempty"`
+    Code        int         `json:"code,omitempty"`
+    Publish     *bool       `json:"publish,omitempty"`
+}
+
+type Exec struct {
+    Kind        string      `json:"kind,omitempty"`
+    Code        *string     `json:"code,omitempty"`
+    Image       string      `json:"image,omitempty"`
+    Init        string      `json:"init,omitempty"`
+    Main        string      `json:"main,omitempty"`
+    Components  []string    `json:"components,omitempty"`    // List of fully qualified actions
+}
+
+type ActionListOptions struct {
+    Limit       int         `url:"limit"`
+    Skip        int         `url:"skip"`
+    Docs        bool        `url:"docs,omitempty"`
+}
+
+////////////////////
+// Action Methods //
+////////////////////
+
+func (s *ActionService) List(packageName string, options *ActionListOptions) ([]Action, *http.Response, error) {
+    var route string
+    var actions []Action
+
+    if (len(packageName) > 0) {
+        // Encode resource name as a path (with no query params) before inserting it into the URI
+        // This way any '?' chars in the name won't be treated as the beginning of the query params
+        packageName = (&url.URL{Path:  packageName}).String()
+        route = fmt.Sprintf("actions/%s/", packageName)
+    } else {
+        route = fmt.Sprintf("actions")
+    }
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgError, "Action list route with options: %s\n", route)
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", routeUrl, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": routeUrl, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    resp, err := s.client.Do(req, &actions, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return actions, resp, err
+}
+
+func (s *ActionService) Insert(action *Action, overwrite bool) (*Action, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    actionName := (&url.URL{Path:  action.Name}).String()
+    route := fmt.Sprintf("actions/%s?overwrite=%t", actionName, overwrite)
+    Debug(DbgInfo, "Action insert route: %s\n", route)
+
+    req, err := s.client.NewRequest("PUT", route, action, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(PUT, %s, %#v) error: '%s'\n", route, err, action)
+        errMsg := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    a := new(Action)
+    resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return a, resp, nil
+}
+
+func (s *ActionService) Get(actionName string) (*Action, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    actionName = (&url.URL{Path: actionName}).String()
+    route := fmt.Sprintf("actions/%s", actionName)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s, nil) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    a := new(Action)
+    resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return a, resp, nil
+}
+
+func (s *ActionService) Delete(actionName string) (*http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    actionName = (&url.URL{Path: actionName}).String()
+    route := fmt.Sprintf("actions/%s", actionName)
+    Debug(DbgInfo, "HTTP route: %s\n", route)
+
+    req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(DELETE, %s, nil) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    a := new(Action)
+    resp, err := s.client.Do(req, a, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return resp, nil
+}
+
+func (s *ActionService) Invoke(actionName string, payload interface{}, blocking bool, result bool) (map[string]interface {}, *http.Response, error) {
+    var res map[string]interface {}
+
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    actionName = (&url.URL{Path: actionName}).String()
+    route := fmt.Sprintf("actions/%s?blocking=%t&result=%t", actionName, blocking, result)
+    Debug(DbgInfo, "HTTP route: %s\n", route)
+
+    req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(POST, %s, %#v) error: '%s'\n", route, payload, err)
+        errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    resp, err := s.client.Do(req, &res, blocking)
+
+    if err != nil {
+      Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+      return res, resp, err
+    }
+
+    return res, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/activation.go b/tools/cli/go-whisk/whisk/activation.go
new file mode 100644
index 0000000..2133878
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/activation.go
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "net/http"
+    "errors"
+    "net/url"
+    "../wski18n"
+)
+
+type ActivationService struct {
+    client *Client
+}
+
+type Activation struct {
+    Namespace       string      `json:"namespace"`
+    Name            string      `json:"name"`
+    Version         string      `json:"version"`
+    Subject         string      `json:"subject"`
+    ActivationID    string      `json:"activationId"`
+    Cause           string      `json:"cause,omitempty"`
+    Start           int64       `json:"start"`        // When action started (in milliseconds since January 1, 1970 UTC)
+    End             int64       `json:"end"`          // Since a 0 is a valid value from server, don't omit
+    Duration        int64       `json:"duration"`     // Only available for actions
+    Response                    `json:"response"`
+    Logs            []string    `json:"logs"`
+    Annotations     KeyValueArr `json:"annotations"`
+    Publish         *bool       `json:"publish,omitempty"`
+
+}
+
+type Response struct {
+    Status     string   `json:"status"`
+    StatusCode int      `json:"statusCode"`
+    Success    bool     `json:"success"`
+    Result     *Result  `json:"result,omitempty"`
+}
+
+type Result map[string]interface{}
+
+type ActivationListOptions struct {
+    Name  string `url:"name,omitempty"`
+    Limit int    `url:"limit"`
+    Skip  int    `url:"skip"`
+    Since int64  `url:"since,omitempty"`
+    Upto  int64  `url:"upto,omitempty"`
+    Docs  bool   `url:"docs,omitempty"`
+}
+
+//MWD - This structure may no longer be needed as the log format is now a string and not JSON
+type Log struct {
+    Log    string `json:"log,omitempty"`
+    Stream string `json:"stream,omitempty"`
+    Time   string `json:"time,omitempty"`
+}
+
+func (s *ActivationService) List(options *ActivationListOptions) ([]Activation, *http.Response, error) {
+    // TODO :: for some reason /activations only works with "_" as namespace
+    s.client.Namespace = "_"
+    route := "activations"
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+            map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+    var activations []Activation
+    resp, err := s.client.Do(req, &activations, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return activations, resp, nil
+
+}
+
+func (s *ActivationService) Get(activationID string) (*Activation, *http.Response, error) {
+    // TODO :: for some reason /activations/:id only works with "_" as namespace
+    s.client.Namespace = "_"
+
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    activationID = (&url.URL{Path: activationID}).String()
+    route := fmt.Sprintf("activations/%s", activationID)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+    a := new(Activation)
+    resp, err := s.client.Do(req, &a, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return a, resp, nil
+}
+
+func (s *ActivationService) Logs(activationID string) (*Activation, *http.Response, error) {
+    // TODO :: for some reason /activations/:id/logs only works with "_" as namespace
+    s.client.Namespace = "_"
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    activationID = (&url.URL{Path: activationID}).String()
+    route := fmt.Sprintf("activations/%s/logs", activationID)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+    activation := new(Activation)
+    resp, err := s.client.Do(req, &activation, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return activation, resp, nil
+}
+
+func (s *ActivationService) Result(activationID string) (*Response, *http.Response, error) {
+    // TODO :: for some reason /activations only works with "_" as namespace
+    s.client.Namespace = "_"
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    activationID = (&url.URL{Path: activationID}).String()
+    route := fmt.Sprintf("activations/%s/result", activationID)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s) error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+    r := new(Response)
+    resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return r, resp, nil
+
+}
diff --git a/tools/cli/go-whisk/whisk/api.go b/tools/cli/go-whisk/whisk/api.go
new file mode 100644
index 0000000..6a04fbf
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/api.go
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "net/http"
+    "errors"
+    "../wski18n"
+)
+
+type ApiService struct {
+    client *Client
+}
+
+// wsk api create : Request, Response
+type ApiCreateRequest struct {
+    ApiDoc          *Api      `json:"apidoc,omitempty"`
+}
+type ApiCreateRequestOptions ApiOptions
+type ApiCreateResponse RetApi
+type ApiCreateResponseV2 RetApiV2
+
+// wsk api list : Request, Response
+type ApiListRequest struct {
+}
+type ApiListRequestOptions struct {
+    ApiOptions
+    Limit           int       `url:"limit"`
+    Skip            int       `url:"skip"`
+    Docs            bool      `url:"docs,omitempty"`
+}
+type ApiListResponse RetApiArray
+type ApiListResponseV2 RetApiArrayV2
+
+// wsk api get : Request, Response
+type ApiGetRequest struct {
+    Api
+}
+type ApiGetRequestOptions ApiOptions
+type ApiGetResponse RetApiArray
+type ApiGetResponseV2 RetApiArrayV2
+
+// wsk api delete : Request, Response
+type ApiDeleteRequest struct {
+    Api
+}
+type ApiDeleteRequestOptions ApiOptions
+type ApiDeleteResponse struct {}
+
+type Api struct {
+    Namespace       string    `json:"namespace,omitempty"`
+    ApiName         string    `json:"apiName,omitempty"`
+    GatewayBasePath string    `json:"gatewayBasePath,omitempty"`
+    GatewayRelPath  string    `json:"gatewayPath,omitempty"`
+    GatewayMethod   string    `json:"gatewayMethod,omitempty"`
+    Id              string    `json:"id,omitempty"`
+    GatewayFullPath string    `json:"gatewayFullPath,omitempty"`
+    Swagger         string    `json:"swagger,omitempty"`
+    Action          *ApiAction `json:"action,omitempty"`
+}
+
+type ApiAction struct {
+    Name            string    `json:"name,omitempty"`
+    Namespace       string    `json:"namespace,omitempty"`
+    BackendMethod   string    `json:"backendMethod,omitempty"`
+    BackendUrl      string    `json:"backendUrl,omitempty"`
+    Auth            string    `json:"authkey,omitempty"`
+}
+
+type ApiOptions struct {
+    ActionName      string    `url:"action,omitempty"`
+    ApiBasePath     string    `url:"basepath,omitempty"`
+    ApiRelPath      string    `url:"relpath,omitempty"`
+    ApiVerb         string    `url:"operation,omitempty"`
+    ApiName         string    `url:"apiname,omitempty"`
+    SpaceGuid       string    `url:"spaceguid,omitempty"`
+    AccessToken     string    `url:"accesstoken,omitempty"`
+    ResponseType    string    `url:"responsetype,omitempty"`
+}
+
+type ApiUserAuth struct {
+    SpaceGuid       string    `json:"spaceguid,omitempty"`
+    AccessToken     string    `json:"accesstoken,omitempty"`
+}
+
+type RetApiArray struct {
+    Apis            []ApiItem `json:"apis,omitempty"`
+}
+
+type ApiItem struct {
+    ApiId           string    `json:"id,omitempty"`
+    QueryKey        string    `json:"key,omitempty"`
+    ApiValue        *RetApi   `json:"value,omitempty"`
+}
+
+type RetApi struct {
+    Namespace       string    `json:"namespace"`
+    BaseUrl         string    `json:"gwApiUrl"`
+    Activated       bool      `json:"gwApiActivated"`
+    TenantId        string    `json:"tenantId"`
+    Swagger         *ApiSwagger `json:"apidoc,omitempty"`
+}
+
+type ApiSwagger struct {
+    SwaggerName     string    `json:"swagger,omitempty"`
+    BasePath        string    `json:"basePath,omitempty"`
+    Info            *ApiSwaggerInfo `json:"info,omitempty"`
+    Paths           map[string]map[string]*ApiSwaggerOperationV1 `json:"paths,omitempty"`
+    XConfig         map[string]map[string][]map[string]map[string]interface{} `json:"x-ibm-configuration,omitempty"`
+}
+
+type RetApiArrayV2 struct {
+    Apis            []ApiItemV2 `json:"apis,omitempty"`
+}
+
+type ApiItemV2 struct {
+    ApiId           string    `json:"id,omitempty"`
+    QueryKey        string    `json:"key,omitempty"`
+    ApiValue        *RetApiV2 `json:"value,omitempty"`
+}
+
+type RetApiV2 struct {
+    Namespace       string    `json:"namespace"`
+    BaseUrl         string    `json:"gwApiUrl"`
+    Activated       bool      `json:"gwApiActivated"`
+    TenantId        string    `json:"tenantId"`
+    Swagger         *ApiSwaggerV2 `json:"apidoc,omitempty"`
+}
+
+type ApiSwaggerV2 struct {
+    SwaggerName     string    `json:"swagger,omitempty"`
+    BasePath        string    `json:"basePath,omitempty"`
+    Info            *ApiSwaggerInfo `json:"info,omitempty"`
+    Paths           map[string]map[string]*ApiSwaggerOperationV2 `json:"paths,omitempty"`  // Paths["/a/path"]["get"] -> a generic object
+    SecurityDef     interface{} `json:"securityDefinitions,omitempty"`
+    Security        interface{} `json:"security,omitempty"`
+    XConfig         interface{} `json:"x-ibm-configuration,omitempty"`
+    XRateLimit      interface{} `json:"x-ibm-rate-limit,omitempty"`
+}
+
+type ApiSwaggerInfo struct {
+    Title           string    `json:"title,omitempty"`
+    Version         string    `json:"version,omitempty"`
+}
+
+type ApiSwaggerOperationV1 struct {
+    Responses       interface{} `json:"responses"`
+    XOpenWhisk      *ApiSwaggerOpXOpenWhiskV1 `json:"x-ibm-op-ext,omitempty"`
+}
+
+type ApiSwaggerOperationV2 struct {
+    OperationId     string    `json:"operationId"`
+    Responses       interface{} `json:"responses"`
+    XOpenWhisk      *ApiSwaggerOpXOpenWhiskV2 `json:"x-openwhisk,omitempty"`
+}
+
+type ApiSwaggerOpXOpenWhiskV1 struct {
+    ActionName      string    `json:"actionName"`
+    Namespace       string    `json:"actionNamespace"`
+    ActionUrlVerb   string    `json:"backendMethod"`
+    ActionUrl       string    `json:"backendUrl"`
+    Policies        interface{} `json:"policies"`
+}
+
+type ApiSwaggerOpXOpenWhiskV2 struct {
+    ActionName      string    `json:"action"`
+    Namespace       string    `json:"namespace"`
+    Package         string    `json:"package"`
+    ApiUrl          string    `json:"url"`
+}
+
+var ApiVerbs map[string]bool = map[string]bool {
+    "GET": true,
+    "PUT": true,
+    "POST": true,
+    "DELETE": true,
+    "PATCH": true,
+    "HEAD": true,
+    "OPTIONS": true,
+}
+
+const (
+    Overwrite = true
+    DoNotOverwrite = false
+)
+
+////////////////////
+// Api Methods //
+////////////////////
+
+func (s *ApiService) List(apiListOptions *ApiListRequestOptions) (*ApiListResponse, *http.Response, error) {
+    route := "web/whisk.system/routemgmt/getApi.http"
+    Debug(DbgInfo, "Api GET/list route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, apiListOptions)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, apiListOptions, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": apiListOptions})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgInfo, "Api GET/list route with api options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", routeUrl, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": routeUrl, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    apiArray := new(ApiListResponse)
+    resp, err := s.client.Do(req, &apiArray, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return apiArray, resp, err
+}
+
+func (s *ApiService) Insert(api *ApiCreateRequest, options *ApiCreateRequestOptions, overwrite bool) (*ApiCreateResponse, *http.Response, error) {
+    route := "web/whisk.system/routemgmt/createApi.http"
+    Debug(DbgInfo, "Api PUT route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgError, "Api create route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("POST", routeUrl, api, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(POST, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    retApi := new(ApiCreateResponse)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return retApi, resp, nil
+}
+
+func (s *ApiService) Get(api *ApiGetRequest, options *ApiGetRequestOptions) (*ApiGetResponse, *http.Response, error) {
+    route := "web/whisk.system/routemgmt/getApi.http"
+    Debug(DbgInfo, "Api GET route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgError, "Api get route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    retApi := new(ApiGetResponse)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return retApi, resp, nil
+}
+
+func (s *ApiService) Delete(api *ApiDeleteRequest, options *ApiDeleteRequestOptions) (*http.Response, error) {
+    route := "web/whisk.system/routemgmt/deleteApi.http"
+    Debug(DbgInfo, "Api DELETE route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    Debug(DbgError, "Api DELETE route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("DELETE", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(DELETE, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    retApi := new(ApiDeleteResponse)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return nil, nil
+}
+
+/////////////
+// V2 Cmds //
+/////////////
+func (s *ApiService) ListV2(apiListOptions *ApiListRequestOptions) (*ApiListResponseV2, *http.Response, error) {
+    route := "web/whisk.system/apimgmt/getApi.http"
+
+    routeUrl, err := addRouteOptions(route, apiListOptions)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, apiListOptions, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": apiListOptions})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgInfo, "Api GET/list route with api options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", routeUrl, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": routeUrl, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    apiArray := new(ApiListResponseV2)
+    resp, err := s.client.Do(req, &apiArray, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    err = validateApiListResponse(apiArray)
+    if err != nil {
+        Debug(DbgError, "Not a valid ApiListReponse object\n")
+        return nil, resp, err
+    }
+
+    return apiArray, resp, err
+}
+
+func (s *ApiService) InsertV2(api *ApiCreateRequest, options *ApiCreateRequestOptions, overwrite bool) (*ApiCreateResponseV2, *http.Response, error) {
+    route := "web/whisk.system/apimgmt/createApi.http"
+    Debug(DbgInfo, "Api PUT route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgError, "Api create route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("POST", routeUrl, api, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(POST, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    retApi := new(ApiCreateResponseV2)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    err = validateApiSwaggerResponse(retApi.Swagger)
+    if err != nil {
+        Debug(DbgError, "Not a valid API creation response\n")
+        return nil, resp, err
+    }
+
+    return retApi, resp, nil
+}
+
+func (s *ApiService) GetV2(api *ApiGetRequest, options *ApiGetRequestOptions) (*ApiGetResponseV2, *http.Response, error) {
+    route := "web/whisk.system/apimgmt/getApi.http"
+    Debug(DbgInfo, "Api GET route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+    Debug(DbgError, "Api get route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, nil, whiskErr
+    }
+
+    retApi := new(ApiGetResponseV2)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return retApi, resp, nil
+}
+
+func (s *ApiService) DeleteV2(api *ApiDeleteRequest, options *ApiDeleteRequestOptions) (*http.Response, error) {
+    route := "web/whisk.system/apimgmt/deleteApi.http"
+    Debug(DbgInfo, "Api DELETE route: %s\n", route)
+
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errMsg := wski18n.T("Unable to add route options '{{.options}}'",
+            map[string]interface{}{"options": options})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+    Debug(DbgError, "Api DELETE route with options: %s\n", routeUrl)
+
+    req, err := s.client.NewRequestUrl("DELETE", routeUrl, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(DELETE, %s, nil, DoNotIncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson) error: '%s'\n", route, err)
+        errMsg := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        whiskErr := MakeWskErrorFromWskError(errors.New(errMsg), err, EXITCODE_ERR_NETWORK, DISPLAY_MSG,
+            NO_DISPLAY_USAGE)
+        return nil, whiskErr
+    }
+
+    retApi := new(ApiDeleteResponse)
+    resp, err := s.client.Do(req, &retApi, ExitWithErrorOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return nil, nil
+}
+
+
+func validateApiListResponse(apiList *ApiListResponseV2) error {
+    for i:=0; i<len(apiList.Apis); i++ {
+        if apiList.Apis[i].ApiValue == nil {
+            Debug(DbgError, "validateApiResponse: No value stanza in api %v\n", apiList.Apis[i])
+            errMsg := wski18n.T("Internal error. Missing value stanza in API configuration response")
+            whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return whiskErr
+        }
+        err := validateApiSwaggerResponse(apiList.Apis[i].ApiValue.Swagger)
+        if (err != nil) {
+            Debug(DbgError, "validateApiListResponse: Invalid Api: %v\n", apiList.Apis[i])
+            return err
+        }
+    }
+    return nil
+}
+
+func validateApiSwaggerResponse(swagger *ApiSwaggerV2) error {
+    if swagger == nil {
+        Debug(DbgError, "validateApiSwaggerResponse: No apidoc stanza in api\n")
+        errMsg := wski18n.T("Internal error. Missing apidoc stanza in API configuration")
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+    for path, _ := range swagger.Paths {
+        err := validateApiPath(swagger.Paths[path])
+        if err != nil {
+            Debug(DbgError, "validateApiResponse: Invalid Api Path object: %v\n", swagger.Paths[path])
+            return err
+        }
+    }
+
+    return nil
+}
+
+func validateApiPath(path map[string]*ApiSwaggerOperationV2) error {
+    for op, opv := range path {
+        err := validateApiOperation(op, opv)
+        if err != nil {
+            Debug(DbgError, "validateApiPath: Invalid Api operation object: %v\n", opv)
+            return err
+        }
+    }
+    return nil
+}
+
+func validateApiOperation(opName string, op *ApiSwaggerOperationV2) error {
+    if len(op.OperationId) == 0 {
+        Debug(DbgError, "validateApiResponse: No operationId field in operation %v\n", op)
+        errMsg := wski18n.T("Missing operationId field in API configuration for operation {{.op}}",
+            map[string]interface{}{"op": opName})
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+    if op.XOpenWhisk == nil {
+        Debug(DbgError, "validateApiResponse: No x-openwhisk stanza in operation %v\n", op)
+        errMsg := wski18n.T("Missing x-openwhisk stanza in API configuration for operation {{.op}}",
+            map[string]interface{}{"op": opName})
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+    if len(op.XOpenWhisk.Namespace) == 0 {
+        Debug(DbgError, "validateApiOperation: no x-openwhisk.namespace stanza in operation %v\n", op)
+        errMsg := wski18n.T("Missing x-openwhisk.namespace field in API configuration for operation {{.op}}",
+            map[string]interface{}{"op": opName})
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    // Note: The op.XOpenWhisk.Package field can have a value of "", so don't enforce a value
+
+    if len(op.XOpenWhisk.ActionName) == 0 {
+        Debug(DbgError, "validateApiOperation: no x-openwhisk.action stanza in operation %v\n", op)
+        errMsg := wski18n.T("Missing x-openwhisk.action field in API configuration for operation {{.op}}",
+            map[string]interface{}{"op": opName})
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+    if len(op.XOpenWhisk.ApiUrl) == 0 {
+        Debug(DbgError, "validateApiOperation: no x-openwhisk.url stanza in operation %v\n", op)
+        errMsg := wski18n.T("Missing x-openwhisk.url field in API configuration for operation {{.op}}",
+            map[string]interface{}{"op": opName})
+        whiskErr := MakeWskError(errors.New(errMsg), EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return whiskErr
+    }
+    return nil
+}
diff --git a/tools/cli/go-whisk/whisk/client.go b/tools/cli/go-whisk/whisk/client.go
new file mode 100644
index 0000000..49222e4
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/client.go
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "bytes"
+    "encoding/base64"
+    "encoding/json"
+    "fmt"
+    "io"
+    "io/ioutil"
+    "net/http"
+    "net/url"
+    "crypto/tls"
+    "errors"
+    "reflect"
+    "../wski18n"
+    "strings"
+)
+
+const (
+    defaultBaseURL = "openwhisk.ng.bluemix.net"
+    AuthRequired = true
+    NoAuth = false
+    IncludeNamespaceInUrl = true
+    DoNotIncludeNamespaceInUrl = false
+    AppendOpenWhiskPathPrefix = true
+    DoNotAppendOpenWhiskPathPrefix = false
+    EncodeBodyAsJson = "json"
+    EncodeBodyAsFormData = "formdata"
+    ProcessTimeOut = true
+    DoNotProcessTimeOut = false
+    ExitWithErrorOnTimeout = true
+    ExitWithSuccessOnTimeout = false
+)
+
+type Client struct {
+    client *http.Client
+    *Config
+    Transport *http.Transport
+
+    Sdks        *SdkService
+    Triggers    *TriggerService
+    Actions     *ActionService
+    Rules       *RuleService
+    Activations *ActivationService
+    Packages    *PackageService
+    Namespaces  *NamespaceService
+    Info        *InfoService
+    Apis        *ApiService
+}
+
+type Config struct {
+    Namespace 	string // NOTE :: Default is "_"
+    AuthToken 	string
+    Host		string
+    BaseURL   	*url.URL // NOTE :: Default is "openwhisk.ng.bluemix.net"
+    Version   	string
+    Verbose   	bool
+    Debug       bool     // For detailed tracing
+    Insecure    bool
+}
+
+func NewClient(httpClient *http.Client, config *Config) (*Client, error) {
+
+    // Disable certificate checking in the dev environment if in insecure mode
+    if config.Insecure {
+        Debug(DbgInfo, "Disabling certificate checking.\n")
+
+        tlsConfig := &tls.Config{
+            InsecureSkipVerify: true,
+        }
+
+        http.DefaultClient.Transport = &http.Transport{
+            TLSClientConfig: tlsConfig,
+        }
+    }
+
+    if httpClient == nil {
+        httpClient = http.DefaultClient
+    }
+
+    var err error
+    if config.BaseURL == nil {
+        config.BaseURL, err = url.Parse(defaultBaseURL)
+        if err != nil {
+            Debug(DbgError, "url.Parse(%s) error: %s\n", defaultBaseURL, err)
+            errStr := wski18n.T("Unable to create request URL '{{.url}}': {{.err}}",
+                map[string]interface{}{"url": defaultBaseURL, "err": err})
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    }
+
+    if len(config.Namespace) == 0 {
+        config.Namespace = "_"
+    }
+
+    if len(config.Version) == 0 {
+        config.Version = "v1"
+    }
+
+    c := &Client{
+        client: httpClient,
+        Config: config,
+    }
+
+    c.Sdks = &SdkService{client: c}
+    c.Triggers = &TriggerService{client: c}
+    c.Actions = &ActionService{client: c}
+    c.Rules = &RuleService{client: c}
+    c.Activations = &ActivationService{client: c}
+    c.Packages = &PackageService{client: c}
+    c.Namespaces = &NamespaceService{client: c}
+    c.Info = &InfoService{client: c}
+    c.Apis = &ApiService{client: c}
+
+    return c, nil
+}
+
+///////////////////////////////
+// Request/Utility Functions //
+///////////////////////////////
+
+func (c *Client) NewRequest(method, urlStr string, body interface{}, includeNamespaceInUrl bool) (*http.Request, error) {
+    if (includeNamespaceInUrl) {
+        if c.Config.Namespace != "" {
+            urlStr = fmt.Sprintf("%s/namespaces/%s/%s", c.Config.Version, c.Config.Namespace, urlStr)
+        } else {
+            urlStr = fmt.Sprintf("%s/namespaces", c.Config.Version)
+        }
+    } else {
+        urlStr = fmt.Sprintf("%s/%s", c.Config.Version, urlStr)
+    }
+
+    urlStr = fmt.Sprintf("%s/%s", c.BaseURL.String(), urlStr)
+    u, err := url.Parse(urlStr)
+    if err != nil {
+        Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+        errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+            map[string]interface{}{"url": urlStr, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    var buf io.ReadWriter
+    if body != nil {
+        buf = new(bytes.Buffer)
+        encoder := json.NewEncoder(buf)
+        encoder.SetEscapeHTML(false)
+        err := encoder.Encode(body)
+
+        if err != nil {
+            Debug(DbgError, "json.Encode(%#v) error: %s\n", body, err)
+            errStr := wski18n.T("Error encoding request body: {{.err}}", map[string]interface{}{"err": err})
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    }
+
+    req, err := http.NewRequest(method, u.String(), buf)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(%v, %s, buf) error: %s\n", method, u.String(), err)
+        errStr := wski18n.T("Error initializing request: {{.err}}", map[string]interface{}{"err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+    if req.Body != nil {
+        req.Header.Add("Content-Type", "application/json")
+    }
+
+    err = c.addAuthHeader(req, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "addAuthHeader() error: %s\n", err)
+        errStr := wski18n.T("Unable to add the HTTP authentication header: {{.err}}",
+            map[string]interface{}{"err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    return req, nil
+}
+
+func (c *Client) addAuthHeader(req *http.Request, authRequired bool) error {
+    if c.Config.AuthToken != "" {
+        encodedAuthToken := base64.StdEncoding.EncodeToString([]byte(c.Config.AuthToken))
+        req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedAuthToken))
+        Debug(DbgInfo, "Adding basic auth header; using authkey\n")
+    } else {
+        if authRequired {
+            Debug(DbgError, "The required authorization key is not configured - neither set as a property nor set via the --auth CLI argument\n")
+            errStr := wski18n.T("Authorization key is not configured (--auth is required)")
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_USAGE, DISPLAY_MSG, DISPLAY_USAGE)
+            return werr
+        }
+    }
+    return nil
+}
+
+// Do sends an API request and returns the API response.  The API response is
+// JSON decoded and stored in the value pointed to by v, or returned as an
+// error if an API error has occurred.  If v implements the io.Writer
+// interface, the raw response body will be written to v, without attempting to
+// first decode it.
+func (c *Client) Do(req *http.Request, v interface{}, ExitWithErrorOnTimeout bool) (*http.Response, error) {
+    var err error
+
+    if IsVerbose() {
+        fmt.Println("REQUEST:")
+        fmt.Printf("[%s]\t%s\n", req.Method, req.URL)
+        if len(req.Header) > 0 {
+            fmt.Println("Req Headers")
+            printJSON(req.Header)
+        }
+        if req.Body != nil {
+            fmt.Println("Req Body")
+            fmt.Println(req.Body)
+            Debug(DbgInfo, "Req Body (ASCII quoted string):\n%+q\n", req.Body)
+        }
+    }
+
+    // Issue the request to the Whisk server endpoint
+    resp, err := c.client.Do(req)
+    if err != nil {
+        Debug(DbgError, "HTTP Do() [req %s] error: %s\n", req.URL.String(), err)
+        werr := MakeWskError(err, EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+    // Don't "defer resp.Body.Close()" here because the body is reloaded to allow caller to
+    // do custom body parsing, such as handling per-route error responses.
+    Verbose("RESPONSE:")
+    Verbose("Got response with code %d\n", resp.StatusCode)
+    if (IsVerbose() && len(resp.Header) > 0) {
+        fmt.Println("Resp Headers")
+        printJSON(resp.Header)
+    }
+
+    // Read the response body
+    data, err := ioutil.ReadAll(resp.Body)
+    if err != nil {
+        Debug(DbgError, "ioutil.ReadAll(resp.Body) error: %s\n", err)
+        werr := MakeWskError(err, EXITCODE_ERR_NETWORK, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return resp, werr
+    }
+    Verbose("Response body size is %d bytes\n", len(data))
+    Verbose("Response body received:\n%s\n", string(data))
+    Debug(DbgInfo, "Response body received (ASCII quoted string):\n%+q\n", string(data))
+
+    // Reload the response body to allow caller access to the body; otherwise,
+    // the caller will have any empty body to read
+    resp.Body = ioutil.NopCloser(bytes.NewBuffer(data))
+
+    // With the HTTP response status code and the HTTP body contents,
+    // the possible response scenarios are:
+    //
+    // 0. HTTP Success + Body indicating a whisk failure result
+    // 1. HTTP Success + Valid body matching request expectations
+    // 2. HTTP Success + No body expected
+    // 3. HTTP Success + Body does NOT match request expectations
+    // 4. HTTP Failure + No body
+    // 5. HTTP Failure + Body matching error format expectation
+    // 6. HTTP Failure + Body NOT matching error format expectation
+
+    // Handle 4. HTTP Failure + No body
+    // If this happens, just return no data and an error
+    if !IsHttpRespSuccess(resp) && data == nil {
+        Debug(DbgError, "HTTP failure %d + no body\n", resp.StatusCode)
+        werr := MakeWskError(errors.New(wski18n.T("Command failed due to an HTTP failure")), resp.StatusCode-256,
+            DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return resp, werr
+    }
+
+    // Handle 5. HTTP Failure + Body matching error format expectation, or body matching a whisk.error() response
+    // Handle 6. HTTP Failure + Body NOT matching error format expectation
+    if !IsHttpRespSuccess(resp) && data != nil {
+        return parseErrorResponse(resp, data, v)
+    }
+
+    // Handle 0. HTTP Success + Body indicating a whisk failure result
+    //   NOTE: Need to ignore activation records send in response to 'wsk get activation NNN` as
+    //         these will report the same original error giving the appearance that the command failed.
+    if (IsHttpRespSuccess(resp) &&                                      // HTTP Status == 200
+        data!=nil &&                                                    // HTTP response body exists
+        v != nil &&
+        !strings.Contains(reflect.TypeOf(v).String(), "Activation") &&  // Request is not `wsk activation get`
+        !IsResponseResultSuccess(data)) {                               // HTTP response body has Whisk error result
+        Debug(DbgInfo, "Got successful HTTP; but activation response reports an error\n")
+        return parseErrorResponse(resp, data, v)
+    }
+
+    // Handle 2. HTTP Success + No body expected
+    if IsHttpRespSuccess(resp) && v == nil {
+        Debug(DbgInfo, "No interface provided; no HTTP response body expected\n")
+        return resp, nil
+    }
+
+    // Handle 1. HTTP Success + Valid body matching request expectations
+    // Handle 3. HTTP Success + Body does NOT match request expectations
+    if IsHttpRespSuccess(resp) && v != nil {
+
+        // If a timeout occurs, 202 HTTP status code is returned, and the caller wishes to handle such an event, return
+        // an error corresponding with the timeout
+        if ExitWithErrorOnTimeout && resp.StatusCode == EXITCODE_TIMED_OUT {
+            errMsg :=  wski18n.T("Request accepted, but processing not completed yet.")
+            err = MakeWskError(errors.New(errMsg), EXITCODE_TIMED_OUT, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+                NO_MSG_DISPLAYED, NO_DISPLAY_PREFIX, NO_APPLICATION_ERR, TIMED_OUT)
+        }
+
+        return parseSuccessResponse(resp, data, v), err
+    }
+
+    // We should never get here, but just in case return failure to keep the compiler happy
+    werr := MakeWskError(errors.New(wski18n.T("Command failed due to an internal failure")), EXITCODE_ERR_GENERAL,
+        DISPLAY_MSG, NO_DISPLAY_USAGE)
+    return resp, werr
+}
+
+func parseErrorResponse(resp *http.Response, data []byte, v interface{}) (*http.Response, error) {
+    Debug(DbgInfo, "HTTP failure %d + body\n", resp.StatusCode)
+
+    // Determine if an application error was received (#5)
+    errorResponse := &ErrorResponse{Response: resp}
+    err := json.Unmarshal(data, errorResponse)
+
+    // Determine if error is an application error or an error generated by API
+    if err == nil {
+        if errorResponse.Code == nil /*&& errorResponse.ErrMsg != nil */&& resp.StatusCode == 502 {
+            return parseApplicationError(resp, data, v)
+        } else if errorResponse.Code != nil && errorResponse.ErrMsg != nil {
+            Debug(DbgInfo, "HTTP failure %d; server error %s\n", resp.StatusCode, errorResponse)
+            werr := MakeWskError(errorResponse, resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return resp, werr
+        }
+    }
+
+    // Body contents are unknown (#6)
+    Debug(DbgError, "HTTP response with unexpected body failed due to contents parsing error: '%v'\n", err)
+    errMsg := wski18n.T("The connection failed, or timed out. (HTTP status code {{.code}})",
+        map[string]interface{}{"code": resp.StatusCode})
+    whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+    return resp, whiskErr
+}
+
+func parseApplicationError(resp *http.Response, data []byte, v interface{}) (*http.Response, error) {
+    Debug(DbgInfo, "Parsing application error\n")
+
+    whiskErrorResponse := &WhiskErrorResponse{}
+    err := json.Unmarshal(data, whiskErrorResponse)
+
+    // Handle application errors that occur when --result option is false (#5)
+    if err == nil && whiskErrorResponse != nil && whiskErrorResponse.Response != nil && whiskErrorResponse.Response.Status != nil {
+        Debug(DbgInfo, "Detected response status `%s` that a whisk.error(\"%#v\") was returned\n",
+            *whiskErrorResponse.Response.Status, *whiskErrorResponse.Response.Result)
+        errMsg := wski18n.T("The following application error was received: {{.err}}",
+            map[string]interface{}{"err": *whiskErrorResponse.Response.Result})
+        whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+            NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
+        return parseSuccessResponse(resp, data, v), whiskErr
+    }
+
+    appErrResult := &AppErrorResult{}
+    err = json.Unmarshal(data, appErrResult)
+
+    // Handle application errors that occur with blocking invocations when --result option is true (#5)
+    if err == nil && appErrResult.Error != nil {
+        Debug(DbgInfo, "Error code is null, blocking with result invocation error has occured\n")
+        errMsg := fmt.Sprintf("%v", *appErrResult.Error)
+        Debug(DbgInfo, "Application error received: %s\n", errMsg)
+
+        whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+            NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
+        return parseSuccessResponse(resp, data, v), whiskErr
+    }
+
+    // Body contents are unknown (#6)
+    Debug(DbgError, "HTTP response with unexpected body failed due to contents parsing error: '%v'\n", err)
+    errMsg := wski18n.T("The connection failed, or timed out. (HTTP status code {{.code}})",
+        map[string]interface{}{"code": resp.StatusCode})
+    whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode - 256, DISPLAY_MSG, NO_DISPLAY_USAGE)
+    return resp, whiskErr
+}
+
+func parseSuccessResponse(resp *http.Response, data []byte, v interface{}) (*http.Response) {
+    Debug(DbgInfo, "Parsing HTTP response into struct type: %s\n", reflect.TypeOf(v))
+
+    dc := json.NewDecoder(strings.NewReader(string(data)))
+    dc.UseNumber()
+    err := dc.Decode(v)
+
+    // If the decode was successful, return the response without error (#1). Otherwise, the decode did not work, so the
+    // server response was unexpected (#3)
+    if err == nil {
+        Debug(DbgInfo, "Successful parse of HTTP response into struct type: %s\n", reflect.TypeOf(v))
+        return resp
+    } else {
+        Debug(DbgWarn, "Unsuccessful parse of HTTP response into struct type: %s; parse error '%v'\n", reflect.TypeOf(v), err)
+        Debug(DbgWarn, "Request was successful, so ignoring the following unexpected response body that could not be parsed: %s\n", data)
+        return resp
+    }
+}
+
+////////////
+// Errors //
+////////////
+
+// For containing the server response body when an error message is returned
+// Here's an example error response body with HTTP status code == 400
+// {
+//     "error": "namespace contains invalid characters",
+//     "code": 1422870
+// }
+type ErrorResponse struct {
+    Response *http.Response                 // HTTP response that caused this error
+    ErrMsg   *interface{}   `json:"error"`  // error message string
+    Code     *int64         `json:"code"`   // validation error code
+}
+
+type AppErrorResult struct {
+    Error  *interface{}    `json:"error"`
+}
+
+type WhiskErrorResponse struct {
+    Response *WhiskResponse `json:"response"`
+}
+
+type WhiskResponse struct {
+    Result  *WhiskResult    `json:"result"`
+    Success bool            `json:"success"`
+    Status  *interface{}    `json:"status"`
+}
+
+type WhiskResult struct {
+//  Error   *WhiskError     `json:"error"`  // whisk.error(<string>) and whisk.reject({msg:<string>}) result in two different kinds of 'error' JSON objects
+}
+
+type WhiskError struct {
+    Msg *string             `json:"msg"`
+}
+
+func (r ErrorResponse) Error() string {
+    return wski18n.T("{{.msg}} (code {{.code}})",
+        map[string]interface{}{"msg": fmt.Sprintf("%v", *r.ErrMsg), "code": r.Code})
+}
+
+////////////////////////////
+// Basic Client Functions //
+////////////////////////////
+
+func IsHttpRespSuccess(r *http.Response) bool {
+    return r.StatusCode >= 200 && r.StatusCode <= 299
+}
+
+func IsResponseResultSuccess(data []byte) bool {
+    errResp := new(WhiskErrorResponse)
+    err := json.Unmarshal(data, &errResp)
+    if (err == nil && errResp.Response != nil) {
+        return errResp.Response.Success
+    }
+    Debug(DbgWarn, "IsResponseResultSuccess: failed to parse response result: %v\n", err)
+    return true;
+}
+
+//
+// Create a HTTP request object using URL stored in url.URL object
+// Arguments:
+//   method         - HTTP verb (i.e. "GET", "PUT", etc)
+//   urlRelResource - *url.URL structure representing the relative resource URL, including query params
+//   body           - optional. Object whose contents will be JSON encoded and placed in HTTP request body
+//   includeNamespaceInUrl - when true "/namespaces/NAMESPACE" is included in the final URL; otherwise not included.
+//   appendOpenWhiskPath - when true, the OpenWhisk URL format is generated
+//   encodeBodyAs   - specifies body encoding (json or form data)
+//   useAuthentication - when true, the basic Authorization is included with the configured authkey as the value
+func (c *Client) NewRequestUrl(
+        method string,
+        urlRelResource *url.URL,
+        body interface{},
+        includeNamespaceInUrl bool,
+        appendOpenWhiskPath bool,
+        encodeBodyAs string,
+        useAuthentication bool) (*http.Request, error) {
+    var requestUrl *url.URL
+    var err error
+
+    if (appendOpenWhiskPath) {
+        var urlVerNamespaceStr string
+        var verPathEncoded = (&url.URL{Path: c.Config.Version}).String()
+
+        if (includeNamespaceInUrl) {
+            if c.Config.Namespace != "" {
+                // Encode path parts before inserting them into the URI so that any '?' is correctly encoded
+                // as part of the path and not the start of the query params
+                verNamespaceEncoded := (&url.URL{Path: c.Config.Namespace}).String()
+                urlVerNamespaceStr = fmt.Sprintf("%s/namespaces/%s", verPathEncoded, verNamespaceEncoded)
+            } else {
+                urlVerNamespaceStr = fmt.Sprintf("%s/namespaces", verPathEncoded)
+            }
+        } else {
+            urlVerNamespaceStr = fmt.Sprintf("%s", verPathEncoded)
+        }
+
+        // Assemble the complete URL: base + version + [namespace] + resource_relative_path
+        Debug(DbgInfo, "basepath: %s, version/namespace path: %s, resource path: %s\n", c.BaseURL.String(), urlVerNamespaceStr, urlRelResource.String())
+        urlStr := fmt.Sprintf("%s/%s/%s", c.BaseURL.String(), urlVerNamespaceStr, urlRelResource.String())
+        requestUrl, err = url.Parse(urlStr)
+        if err != nil {
+            Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+            errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+                map[string]interface{}{"url": urlStr, "err": err})
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    } else {
+        Debug(DbgInfo, "basepath: %s, resource path: %s\n", c.BaseURL.String(), urlRelResource.String())
+        urlStr := fmt.Sprintf("%s/%s", c.BaseURL.String(), urlRelResource.String())
+        requestUrl, err = url.Parse(urlStr)
+        if err != nil {
+            Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+            errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+                map[string]interface{}{"url": urlStr, "err": err})
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    }
+
+    var buf io.ReadWriter
+    if body != nil {
+        if (encodeBodyAs == EncodeBodyAsJson) {
+            buf = new(bytes.Buffer)
+            encoder := json.NewEncoder(buf)
+            encoder.SetEscapeHTML(false)
+            err := encoder.Encode(body)
+
+            if err != nil {
+                Debug(DbgError, "json.Encode(%#v) error: %s\n", body, err)
+                errStr := wski18n.T("Error encoding request body: {{.err}}",
+                    map[string]interface{}{"err": err})
+                werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+                return nil, werr
+            }
+        } else if (encodeBodyAs == EncodeBodyAsFormData) {
+            if values, ok := body.(url.Values); ok {
+                buf = bytes.NewBufferString(values.Encode())
+            } else {
+                Debug(DbgError, "Invalid form data body: %v\n", body)
+                errStr := wski18n.T("Internal error.  Form data encoding failure")
+                werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+                return nil, werr
+            }
+        } else {
+            Debug(DbgError, "Invalid body encode type: %s\n", encodeBodyAs)
+            errStr := wski18n.T("Internal error.  Invalid encoding type '{{.encodetype}}'",
+                map[string]interface{}{"encodetype": encodeBodyAs})
+            werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    }
+
+    req, err := http.NewRequest(method, requestUrl.String(), buf)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(%v, %s, buf) error: %s\n", method, requestUrl.String(), err)
+        errStr := wski18n.T("Error initializing request: {{.err}}", map[string]interface{}{"err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+    if (req.Body != nil && encodeBodyAs == EncodeBodyAsJson) {
+        req.Header.Add("Content-Type", "application/json")
+    }
+    if (req.Body != nil && encodeBodyAs == EncodeBodyAsFormData) {
+        req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+    }
+
+    if useAuthentication {
+        err = c.addAuthHeader(req, AuthRequired)
+        if err != nil {
+            Debug(DbgError, "addAuthHeader() error: %s\n", err)
+            errStr := wski18n.T("Unable to add the HTTP authentication header: {{.err}}",
+                map[string]interface{}{"err": err})
+            werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+            return nil, werr
+        }
+    } else {
+        Debug(DbgInfo, "No auth header required\n")
+    }
+
+    return req, nil
+}
diff --git a/tools/cli/go-whisk/whisk/info.go b/tools/cli/go-whisk/whisk/info.go
new file mode 100644
index 0000000..815e753
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/info.go
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "net/http"
+    "net/url"
+    "errors"
+    "../wski18n"
+    "fmt"
+)
+
+type Info struct {
+    Whisk   string `json:"whisk,omitempty"`
+    Version string `json:"version,omitempty"`
+    Build   string `json:"build,omitempty"`
+    BuildNo string `json:"buildno,omitempty"`
+}
+
+type InfoService struct {
+    client *Client
+}
+
+func (s *InfoService) Get() (*Info, *http.Response, error) {
+    // make a request to c.BaseURL / v1
+    urlStr := fmt.Sprintf("%s/%s", s.client.BaseURL.String(), s.client.Config.Version)
+    u, err := url.Parse(urlStr)
+    if err != nil {
+        Debug(DbgError, "url.Parse(%s) error: %s\n", urlStr, err)
+        errStr := wski18n.T("Unable to URL parse '{{.version}}': {{.err}}",
+            map[string]interface{}{"version": urlStr, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := http.NewRequest("GET", u.String(), nil)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s) error: %s\n", u.String(), err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+            map[string]interface{}{"url": u.String(), "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    Debug(DbgInfo, "Sending HTTP URL '%s'; req %#v\n", req.URL.String(), req)
+    info := new(Info)
+    resp, err := s.client.Do(req, &info, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, nil, err
+    }
+
+    return info, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/namespace.go b/tools/cli/go-whisk/whisk/namespace.go
new file mode 100644
index 0000000..5db8e2a
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/namespace.go
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "net/http"
+    "errors"
+    "../wski18n"
+)
+
+type Namespace struct {
+    Name                string  `json:"name"`
+    Contents                    `json:"contents,omitempty"`
+}
+
+type Contents struct {
+    Actions  []Action       `json:"actions"`
+    Packages []Package      `json:"packages"`
+    Triggers []Trigger      `json:"triggers"`
+    Rules    []Rule         `json:"rules"`
+}
+
+type NamespaceService struct {
+    client *Client
+}
+
+// get a list of available namespaces
+func (s *NamespaceService) List() ([]Namespace, *http.Response, error) {
+    // make a request to c.BaseURL / namespaces
+
+    // Create the request against the namespaces resource
+    s.client.Config.Namespace = ""
+    route := ""
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "s.client.NewRequest(GET) error: %s\n", err)
+        errStr := wski18n.T("Unable to create HTTP request for GET: {{.err}}",
+            map[string]interface{}{"err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    var namespaceNames []string
+    resp, err := s.client.Do(req, &namespaceNames, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    var namespaces []Namespace
+    for _, nsName := range namespaceNames {
+        ns := Namespace{
+            Name: nsName,
+        }
+        namespaces = append(namespaces, ns)
+    }
+
+    Debug(DbgInfo, "Returning []namespaces: %#v\n", namespaces)
+    return namespaces, resp, nil
+}
+
+func (s *NamespaceService) Get(namespace string) (*Namespace, *http.Response, error) {
+
+    if len(namespace) == 0 {
+        namespace = s.client.Config.Namespace
+    }
+
+    s.client.Namespace = namespace
+    resNamespace := &Namespace{
+        Name: namespace,
+    }
+
+    req, err := s.client.NewRequest("GET", "", nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "s.client.NewRequest(GET) error: %s\n", err)
+        errStr := wski18n.T("Unable to create HTTP request for GET: {{.err}}", map[string]interface{}{"err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return resNamespace, nil, werr
+    }
+
+    resp, err := s.client.Do(req, &resNamespace.Contents, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return resNamespace, resp, err
+    }
+
+    Debug(DbgInfo, "Returning namespace: %#v\n", resNamespace)
+
+    return resNamespace, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/package.go b/tools/cli/go-whisk/whisk/package.go
new file mode 100644
index 0000000..2485f41
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/package.go
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "net/http"
+    "net/url"
+    "errors"
+    "../wski18n"
+)
+
+type PackageService struct {
+    client *Client
+}
+
+type PackageInterface interface {
+    GetName() string
+}
+
+// Use this struct to represent the package/binding sent from the Whisk server
+// Binding is a bool ???MWD20160602 now seeing Binding as a struct???
+type Package struct {
+    Namespace   string              `json:"namespace,omitempty"`
+    Name        string              `json:"name,omitempty"`
+    Version     string              `json:"version,omitempty"`
+    Publish     *bool               `json:"publish,omitempty"`
+    Annotations KeyValueArr         `json:"annotations,omitempty"`
+    Parameters  KeyValueArr         `json:"parameters,omitempty"`
+    Binding     *Binding            `json:"binding,omitempty"`
+    Actions     []Action            `json:"actions,omitempty"`
+    Feeds       []Action            `json:"feeds,omitempty"`
+}
+func (p *Package) GetName() string {
+    return p.Name
+}
+
+// Use this struct when creating a binding
+// Publish is NOT optional; Binding is a namespace/name object, not a bool
+type BindingPackage struct {
+    Namespace   string              `json:"-"`
+    Name        string              `json:"-"`
+    Version     string              `json:"version,omitempty"`
+    Publish     *bool               `json:"publish,omitempty"`
+    Annotations KeyValueArr         `json:"annotations,omitempty"`
+    Parameters  KeyValueArr         `json:"parameters,omitempty"`
+    Binding                         `json:"binding"`
+}
+func (p *BindingPackage) GetName() string {
+    return p.Name
+}
+
+type Binding struct {
+    Namespace   string              `json:"namespace,omitempty"`
+    Name        string              `json:"name,omitempty"`
+}
+
+type BindingUpdates struct {
+    Added       []string            `json:"added,omitempty"`
+    Updated     []string            `json:"updated,omitempty"`
+    Deleted     []string            `json:"deleted,omitempty"`
+}
+
+type PackageListOptions struct {
+    Public      bool                `url:"public,omitempty"`
+    Limit       int                 `url:"limit"`
+    Skip        int                 `url:"skip"`
+    Since       int                 `url:"since,omitempty"`
+    Docs        bool                `url:"docs,omitempty"`
+}
+
+func (s *PackageService) List(options *PackageListOptions) ([]Package, *http.Response, error) {
+    route := fmt.Sprintf("packages")
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errStr := wski18n.T("Unable to build request URL: {{.err}}", map[string]interface{}{"err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    var packages []Package
+    resp, err := s.client.Do(req, &packages, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return packages, resp, err
+
+}
+
+func (s *PackageService) Get(packageName string) (*Package, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    packageName = (&url.URL{Path: packageName}).String()
+    route := fmt.Sprintf("packages/%s", packageName)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    p := new(Package)
+    resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return p, resp, nil
+
+}
+
+func (s *PackageService) Insert(x_package PackageInterface, overwrite bool) (*Package, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    packageName := (&url.URL{Path: x_package.GetName()}).String()
+    route := fmt.Sprintf("packages/%s?overwrite=%t", packageName, overwrite)
+
+    req, err := s.client.NewRequest("PUT", route, x_package, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(PUT, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create PUT HTTP request for '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    p := new(Package)
+    resp, err := s.client.Do(req, &p, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return p, resp, nil
+}
+
+func (s *PackageService) Delete(packageName string) (*http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    packageName = (&url.URL{Path: packageName}).String()
+    route := fmt.Sprintf("packages/%s", packageName)
+
+    req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create DELETE HTTP request for '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return resp, nil
+}
+
+func (s *PackageService) Refresh() (*BindingUpdates, *http.Response, error) {
+    route := "packages/refresh"
+
+    req, err := s.client.NewRequest("POST", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create POST HTTP request for '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    updates := &BindingUpdates{}
+    resp, err := s.client.Do(req, updates, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return updates, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/rule.go b/tools/cli/go-whisk/whisk/rule.go
new file mode 100644
index 0000000..eb6d04d
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/rule.go
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "net/http"
+    "strings"
+    "errors"
+    "net/url"
+    "../wski18n"
+)
+
+type RuleService struct {
+    client *Client
+}
+
+type Rule struct {
+    Namespace string    `json:"namespace,omitempty"`
+    Name      string    `json:"name,omitempty"`
+    Version   string    `json:"version,omitempty"`
+    Status  string      `json:"status"`
+    Trigger interface{} `json:"trigger"`
+    Action  interface{} `json:"action"`
+    Publish *bool       `json:"publish,omitempty"`
+
+}
+
+type RuleListOptions struct {
+    Limit       int     `url:"limit"`
+    Skip        int     `url:"skip"`
+    Docs        bool    `url:"docs,omitempty"`
+}
+
+func (s *RuleService) List(options *RuleListOptions) ([]Rule, *http.Response, error) {
+    route := "rules"
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+            map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    var rules []Rule
+    resp, err := s.client.Do(req, &rules, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return rules, resp, err
+}
+
+func (s *RuleService) Insert(rule *Rule, overwrite bool) (*Rule, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    ruleName := (&url.URL{Path: rule.Name}).String()
+    route := fmt.Sprintf("rules/%s?overwrite=%t", ruleName, overwrite)
+
+    req, err := s.client.NewRequest("PUT", route, rule, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(PUT, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    r := new(Rule)
+    resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return r, resp, nil
+}
+
+func (s *RuleService) Get(ruleName string) (*Rule, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    ruleName = (&url.URL{Path: ruleName}).String()
+    route := fmt.Sprintf("rules/%s", ruleName)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    r := new(Rule)
+    resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return r, resp, nil
+}
+
+func (s *RuleService) Delete(ruleName string) (*http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    ruleName = (&url.URL{Path: ruleName}).String()
+    route := fmt.Sprintf("rules/%s", ruleName)
+
+    req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    resp, err := s.client.Do(req, nil, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return resp, nil
+}
+
+func (s *RuleService) SetState(ruleName string, state string) (*Rule, *http.Response, error) {
+    state = strings.ToLower(state)
+    if state != "active" && state != "inactive" {
+        errStr := wski18n.T("Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".",
+            map[string]interface{}{"state": state})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    ruleName = (&url.URL{Path: ruleName}).String()
+    route := fmt.Sprintf("rules/%s", ruleName)
+
+    ruleState := &Rule{ Status: state }
+
+    req, err := s.client.NewRequest("POST", route, ruleState, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(POST, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    r := new(Rule)
+    resp, err := s.client.Do(req, &r, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return r, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/sdk.go b/tools/cli/go-whisk/whisk/sdk.go
new file mode 100644
index 0000000..d4e2859
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/sdk.go
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "errors"
+    "net/http"
+    "../wski18n"
+)
+
+type SdkService struct {
+    client *Client
+}
+
+// Structure for SDK request responses
+type Sdk struct {
+    // TODO :: Add SDK fields
+}
+
+type SdkRequest struct {
+    // TODO :: Add SDK
+}
+
+// Install artifact {component = docker || swift || iOS}
+func (s *SdkService) Install(relFileUrl string) (*http.Response, error) {
+
+    urlStr := fmt.Sprintf("https://%s/%s", s.client.Config.BaseURL.Host, relFileUrl)
+
+    req, err := http.NewRequest("GET", urlStr, nil)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s, nil) error: %s\n", urlStr, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+            map[string]interface{}{"url": urlStr, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    if IsVerbose() {
+        fmt.Println("REQUEST:")
+        fmt.Printf("[%s]\t%s\n", req.Method, req.URL)
+        if len(req.Header) > 0 {
+            fmt.Println("Req Headers")
+            printJSON(req.Header)
+        }
+        if req.Body != nil {
+            fmt.Println("Req Body")
+            fmt.Println(req.Body)
+        }
+    }
+
+    // Directly use the HTTP client, not the Whisk CLI client, so that the response body is left alone
+    resp, err := s.client.client.Do(req)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return resp, err
+    }
+
+    return resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/shared.go b/tools/cli/go-whisk/whisk/shared.go
new file mode 100644
index 0000000..46094d6
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/shared.go
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import "encoding/json"
+
+type KeyValue struct {
+    Key  string         `json:"key"`
+    Value interface{}   `json:"value"`
+}
+
+type KeyValueArr []KeyValue
+
+type Annotations []map[string]interface{}
+
+type Parameters *json.RawMessage
+
+type Limits struct {
+    Timeout *int `json:"timeout,omitempty"`
+    Memory  *int `json:"memory,omitempty"`
+    Logsize *int `json:"logs,omitempty"`
+}
diff --git a/tools/cli/go-whisk/whisk/trace.go b/tools/cli/go-whisk/whisk/trace.go
new file mode 100644
index 0000000..1e35417
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/trace.go
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "runtime"
+    "strings"
+    "os"
+)
+
+type DebugLevel string
+const (
+    DbgInfo    DebugLevel = "Inf"
+    DbgWarn    DebugLevel = "Wrn"
+    DbgError   DebugLevel = "Err"
+    DbgFatal   DebugLevel = "Ftl"
+)
+
+const MaxNameLen int = 25
+
+var isVerbose bool
+var isDebug bool
+
+func init() {
+    if len(os.Getenv("WSK_CLI_DEBUG")) > 0 {    // Useful for tracing init() code, before parms are parsed
+        SetDebug(true)
+    }
+}
+
+func SetDebug(b bool) {
+    isDebug = b
+}
+
+func SetVerbose (b bool) {
+    isVerbose = b
+}
+
+func IsVerbose() bool {
+    return isVerbose || isDebug
+}
+
+/* Function for tracing debug level messages to stdout
+   Output format:
+   [file-or-function-name]:line-#:[DebugLevel] The formated message without any appended \n
+ */
+func Debug(dl DebugLevel, msgFormat string, args ...interface{}) {
+    if isDebug {
+        pc, file, line, _ := runtime.Caller(1)
+        fcn := runtime.FuncForPC(pc)
+        msg := fmt.Sprintf(msgFormat, args...)
+        fcnName := fcn.Name()
+
+        // Cobra command Run/RunE functions are anonymous, so the function name is unfriendly,
+        // so use a file name instead
+        if strings.Contains(fcnName, "commands.glob.") || strings.Contains(fcnName, "whisk.glob.") {
+            fcnName = file
+        }
+
+        // Only interesting the the trailing function/file name characters
+        if len(fcnName) > MaxNameLen {
+            fcnName = fcnName[len(fcnName)-MaxNameLen:]
+        }
+        fmt.Printf("[%-25s]:%03d:[%3s] %v", fcnName, line, dl, msg)
+    }
+}
+
+/* Function for tracing debug level messages to stdout
+   Output format:
+   [file-or-function-name]:line-#:[DebugLevel] The formated message without any appended \n
+ */
+func Verbose(msgFormat string, args ...interface{}) {
+    if IsVerbose() {
+        msg := fmt.Sprintf(msgFormat, args...)
+        fmt.Printf("%v", msg)
+    }
+}
+
+
diff --git a/tools/cli/go-whisk/whisk/trigger.go b/tools/cli/go-whisk/whisk/trigger.go
new file mode 100644
index 0000000..6d800e9
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/trigger.go
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "fmt"
+    "net/http"
+    "errors"
+    "net/url"
+    "../wski18n"
+)
+
+type TriggerService struct {
+    client *Client
+}
+
+type Trigger struct {
+    Namespace       string          `json:"namespace,omitempty"`
+    Name            string          `json:"name,omityempty"`
+    Version         string          `json:"version,omitempty"`
+    ActivationId    string          `json:"activationId,omitempty"`
+    Annotations     KeyValueArr     `json:"annotations,omitempty"`
+    Parameters      KeyValueArr     `json:"parameters,omitempty"`
+    Limits          *Limits         `json:"limits,omitempty"`
+    Publish         *bool           `json:"publish,omitempty"`
+
+}
+
+type TriggerListOptions struct {
+    Limit           int             `url:"limit"`
+    Skip            int             `url:"skip"`
+    Docs            bool            `url:"docs,omitempty"`
+}
+
+func (s *TriggerService) List(options *TriggerListOptions) ([]Trigger, *http.Response, error) {
+    route := "triggers"
+    routeUrl, err := addRouteOptions(route, options)
+    if err != nil {
+        Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+        errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+            map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    var triggers []Trigger
+    resp, err := s.client.Do(req, &triggers, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return triggers, resp, nil
+}
+
+func (s *TriggerService) Insert(trigger *Trigger, overwrite bool) (*Trigger, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    triggerName := (&url.URL{Path:  trigger.Name}).String()
+    route := fmt.Sprintf("triggers/%s?overwrite=%t", triggerName, overwrite)
+
+    routeUrl, err := url.Parse(route)
+    if err != nil {
+        Debug(DbgError, "url.Parse(%s) error: %s\n", route, err)
+        errStr := wski18n.T("Invalid request URL '{{.url}}': {{.err}}",
+            map[string]interface{}{"url": route, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    req, err := s.client.NewRequestUrl("PUT", routeUrl, trigger, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+    if err != nil {
+        Debug(DbgError, "http.NewRequestUrl(PUT, %s, %+v, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired); error: '%s'\n", routeUrl, trigger, err)
+        errStr := wski18n.T("Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": routeUrl, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    t := new(Trigger)
+    resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return t, resp, nil
+
+}
+
+func (s *TriggerService) Get(triggerName string) (*Trigger, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    triggerName = (&url.URL{Path: triggerName}).String()
+    route := fmt.Sprintf("triggers/%s", triggerName)
+
+    req, err := s.client.NewRequest("GET", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(GET, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    t := new(Trigger)
+    resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return t, resp, nil
+
+}
+
+func (s *TriggerService) Delete(triggerName string) (*Trigger, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    triggerName = (&url.URL{Path: triggerName}).String()
+    route := fmt.Sprintf("triggers/%s", triggerName)
+
+    req, err := s.client.NewRequest("DELETE", route, nil, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError, "http.NewRequest(DELETE, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    t := new(Trigger)
+    resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return t, resp, nil
+}
+
+func (s *TriggerService) Fire(triggerName string, payload interface{}) (*Trigger, *http.Response, error) {
+    // Encode resource name as a path (with no query params) before inserting it into the URI
+    // This way any '?' chars in the name won't be treated as the beginning of the query params
+    triggerName = (&url.URL{Path: triggerName}).String()
+    route := fmt.Sprintf("triggers/%s", triggerName)
+
+    req, err := s.client.NewRequest("POST", route, payload, IncludeNamespaceInUrl)
+    if err != nil {
+        Debug(DbgError," http.NewRequest(POST, %s); error: '%s'\n", route, err)
+        errStr := wski18n.T("Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, nil, werr
+    }
+
+    t := new(Trigger)
+    resp, err := s.client.Do(req, &t, ExitWithSuccessOnTimeout)
+    if err != nil {
+        Debug(DbgError, "s.client.Do() error - HTTP req %s; error: '%s'\n", req.URL.String(), err)
+        return nil, resp, err
+    }
+
+    return t, resp, nil
+}
diff --git a/tools/cli/go-whisk/whisk/util.go b/tools/cli/go-whisk/whisk/util.go
new file mode 100644
index 0000000..ce78697
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/util.go
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+import (
+    "errors"
+    "fmt"
+    "net/url"
+    "reflect"
+
+    "github.com/fatih/color"
+    "github.com/google/go-querystring/query"
+    "github.com/hokaccha/go-prettyjson"
+    "../wski18n"
+)
+
+// addOptions adds the parameters in opt as URL query parameters to s.  opt
+// must be a struct whose fields may contain "url" tags.
+func addRouteOptions(route string, options interface{}) (*url.URL, error) {
+    Debug(DbgInfo, "Adding options %+v to route '%s'\n", options, route)
+    u, err := url.Parse(route)
+    if err != nil {
+        Debug(DbgError,"url.Parse(%s) error: %s\n", route, err)
+        errStr := wski18n.T("Unable to parse URL '{{.route}}': {{.err}}",
+            map[string]interface{}{"route": route, "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    v := reflect.ValueOf(options)
+    if v.Kind() == reflect.Ptr && v.IsNil() {
+        return u, nil
+    }
+
+    qs, err := query.Values(options)
+    if err != nil {
+        Debug(DbgError,"query.Values(%#v) error: %s\n", options, err)
+        errStr := wski18n.T("Unable to process URL query options '{{.options}}': {{.err}}",
+            map[string]interface{}{"options": fmt.Sprintf("%#v", options), "err": err})
+        werr := MakeWskError(errors.New(errStr), EXITCODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+        return nil, werr
+    }
+
+    u.RawQuery = qs.Encode()
+    Debug(DbgInfo,"Returning route options '%s' from input struct %+v\n", u.String(), options)
+    return u, nil
+}
+
+func printJSON(v interface{}) {
+    output, _ := prettyjson.Marshal(v)
+    fmt.Fprintln(color.Output, string(output))
+}
diff --git a/tools/cli/go-whisk/whisk/wskerror.go b/tools/cli/go-whisk/whisk/wskerror.go
new file mode 100644
index 0000000..6969152
--- /dev/null
+++ b/tools/cli/go-whisk/whisk/wskerror.go
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package whisk
+
+const EXITCODE_ERR_GENERAL      int = 1
+const EXITCODE_ERR_USAGE        int = 2
+const EXITCODE_ERR_NETWORK      int = 3
+const EXITCODE_ERR_HTTP_RESP    int = 4
+const NOT_ALLOWED               int = 149
+const EXITCODE_TIMED_OUT        int = 202
+
+const DISPLAY_MSG               bool = true
+const NO_DISPLAY_MSG            bool = false
+const DISPLAY_USAGE             bool = true
+const NO_DISPLAY_USAGE          bool = false
+const NO_MSG_DISPLAYED          bool = false
+const DISPLAY_PREFIX            bool = true
+const NO_DISPLAY_PREFIX         bool = false
+const APPLICATION_ERR           bool = true
+const NO_APPLICATION_ERR        bool = false
+const TIMED_OUT                 bool = true
+
+type WskError struct {
+    RootErr             error   // Parent error
+    ExitCode            int     // Error code to be returned to the OS
+    DisplayMsg          bool    // When true, the error message should be displayed to console
+    MsgDisplayed        bool    // When true, the error message has already been displayed, don't display it again
+    DisplayUsage        bool    // When true, the CLI usage should be displayed before exiting
+    DisplayPrefix       bool    // When true, the CLI will prefix an error message with "error: "
+    ApplicationError    bool    // When true, the error is a result of an application failure
+    TimedOut            bool    // When True, the error is a result of a timeout
+}
+
+/*
+Prints the error message contained inside an WskError. An error prefix may, or may not be displayed depending on the
+WskError's setting for DisplayPrefix.
+
+Parameters:
+    err     - WskError object used to display an error message from
+ */
+func (whiskError WskError) Error() string {
+    return whiskError.RootErr.Error()
+}
+
+/*
+Instantiate a WskError structure
+Parameters:
+    error   - RootErr. object implementing the error interface
+    int     - ExitCode.  Used if error object does not have an exit code OR if ExitCodeOverride is true
+    bool    - DisplayMsg.  If true, the error message should be displayed on the console
+    bool    - DisplayUsage.  If true, the command usage syntax/help should be displayed on the console
+    bool    - MsgDisplayed.  If true, the error message has been displayed on the console
+    bool    - DisplayPreview.  If true, the error message will be prefixed with "error: "
+    bool    - TimedOut. If true, the error is a result of a timeout
+*/
+func MakeWskError (err error, exitCode int, flags ...bool ) (resWhiskError *WskError) {
+    resWhiskError = &WskError{
+        RootErr: err,
+        ExitCode: exitCode,
+        DisplayMsg: false,
+        DisplayUsage: false,
+        MsgDisplayed: false,
+        DisplayPrefix: true,
+        ApplicationError: false,
+        TimedOut: false,
+    }
+
+    if len(flags) > 0 { resWhiskError.DisplayMsg = flags[0] }
+    if len(flags) > 1 { resWhiskError.DisplayUsage = flags[1] }
+    if len(flags) > 2 { resWhiskError.MsgDisplayed = flags[2] }
+    if len(flags) > 3 { resWhiskError.DisplayPrefix = flags[3] }
+    if len(flags) > 4 { resWhiskError.ApplicationError = flags[4] }
+    if len(flags) > 5 { resWhiskError.TimedOut = flags[5] }
+
+    return resWhiskError
+}
+
+/*
+Instantiate a WskError structure
+Parameters:
+    error       - RootErr. object implementing the error interface
+    WskError    - WskError being wrappered.  It's exitcode will be used as this WskError's exitcode.  Ignored if nil
+    int         - ExitCode. Used if error object is nil or if the error object is not a WskError
+    bool        - DisplayMsg. If true, the error message should be displayed on the console
+    bool        - DisplayUsage. If true, the command usage syntax/help should be displayed on the console
+    bool        - MsgDisplayed. If true, the error message has been displayed on the console
+    bool        - ApplicationError. If true, the error is a result of an application error
+    bool        - TimedOut. If true, the error resulted from a timeout
+*/
+func MakeWskErrorFromWskError (baseError error, whiskError error, exitCode int, flags ...bool) (resWhiskError *WskError) {
+
+    // Get the exit code, and flags from the existing Whisk error
+    if whiskError != nil {
+
+        // Ensure the Whisk error is a pointer
+        switch errorType := whiskError.(type) {
+            case *WskError:
+                resWhiskError = errorType
+            case WskError:
+                resWhiskError = &errorType
+        }
+
+        if resWhiskError != nil {
+            exitCode, flags = getWhiskErrorProperties(resWhiskError, flags...)
+        }
+    }
+
+    return MakeWskError(baseError, exitCode, flags...)
+}
+
+/*
+Returns the settings from a WskError. Values returned will include ExitCode, DisplayMsg, DisplayUsage, MsgDisplayed,
+DisplayPrefix, TimedOut.
+
+Parameters:
+    whiskError  - WskError to examine.
+    flags       - Boolean values that may override the WskError object's values for DisplayMsg, DisplayUsage,
+                    MsgDisplayed, ApplicationError, TimedOut.
+ */
+func getWhiskErrorProperties(whiskError *WskError, flags ...bool) (int, []bool) {
+    if len(flags) > 0 {
+        flags[0] = whiskError.DisplayMsg
+    } else {
+        flags = append(flags, whiskError.DisplayMsg)
+    }
+
+    if len(flags) > 1 {
+        flags[1] = whiskError.DisplayUsage || flags[1]
+    } else {
+        flags = append(flags, whiskError.DisplayUsage)
+    }
+
+    if len(flags) > 2 {
+        flags[2] = whiskError.MsgDisplayed || flags[2]
+    } else {
+        flags = append(flags, whiskError.MsgDisplayed)
+    }
+
+    if len(flags) > 3 {
+        flags[3] = whiskError.DisplayPrefix || flags[3]
+    } else {
+        flags = append(flags, whiskError.DisplayPrefix)
+    }
+
+    if len(flags) > 4 {
+        flags[4] = whiskError.ApplicationError || flags[4]
+    } else {
+        flags = append(flags, whiskError.ApplicationError)
+    }
+
+    if len(flags) > 5 {
+        flags[5] = whiskError.TimedOut || flags[5]
+    } else {
+        flags = append(flags, whiskError.TimedOut)
+    }
+
+    return whiskError.ExitCode, flags
+}
diff --git a/tools/cli/go-whisk/wski18n/detection.go b/tools/cli/go-whisk/wski18n/detection.go
new file mode 100644
index 0000000..03d9be8
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/detection.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package wski18n
+
+import "github.com/cloudfoundry/jibber_jabber"
+
+type Detector interface {
+    DetectLocale() string
+    DetectLanguage() string
+}
+
+type JibberJabberDetector struct{}
+
+func (d *JibberJabberDetector) DetectLocale() string {
+    userLocale, err := jibber_jabber.DetectIETF()
+    if err != nil {
+        userLocale = ""
+    }
+    return userLocale
+}
+
+func (d *JibberJabberDetector) DetectLanguage() string {
+    lang, err := jibber_jabber.DetectLanguage()
+    if err != nil {
+        lang = ""
+    }
+    return lang
+}
diff --git a/tools/cli/go-whisk/wski18n/i18n.go b/tools/cli/go-whisk/wski18n/i18n.go
new file mode 100644
index 0000000..3c68b5f
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/i18n.go
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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.
+ */
+
+package wski18n
+
+import (
+    "path/filepath"
+    "strings"
+
+    goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+const (
+    DEFAULT_LOCALE = "en_US"
+)
+
+var SUPPORTED_LOCALES = []string{
+    "de_DE",
+    "en_US",
+    "es_ES",
+    "fr_FR",
+    "it_IT",
+    "ja_JA",
+    "ko_KR",
+    "pt_BR",
+    "zh_Hans",
+    "zh_Hant",
+}
+
+var resourcePath = filepath.Join("wski18n", "resources")
+
+func GetResourcePath() string {
+    return resourcePath
+}
+
+func SetResourcePath(path string) {
+    resourcePath = path
+}
+
+var T goi18n.TranslateFunc
+var curLocale string
+
+func init() {
+    curLocale = Init(new(JibberJabberDetector))
+}
+
+func CurLocale() string {
+    return curLocale
+}
+
+func Locale(detector Detector) string {
+
+    // Use default locale until strings are translated
+    /*sysLocale := normalize(detector.DetectLocale())
+    if isSupported(sysLocale) {
+        return sysLocale
+    }
+
+    locale := defaultLocaleForLang(detector.DetectLanguage())
+    if locale != "" {
+        return locale
+    }*/
+
+    return DEFAULT_LOCALE
+}
+
+func Init(detector Detector) string {
+    l := Locale(detector)
+    InitWithLocale(l)
+    return l
+}
+
+func InitWithLocale(locale string) {
+    err := loadFromAsset(locale)
+    if err != nil {
+        panic(err)
+    }
+    T = goi18n.MustTfunc(locale)
+}
+
+func loadFromAsset(locale string) (err error) {
+    assetName := locale + ".all.json"
+    assetKey := filepath.Join(resourcePath, assetName)
+    bytes, err := Asset(assetKey)
+    if err != nil {
+        return
+    }
+    err = goi18n.ParseTranslationFileBytes(assetName, bytes)
+    return
+}
+
+func normalize(locale string) string {
+    locale = strings.ToLower(strings.Replace(locale, "-", "_", 1))
+    for _, l := range SUPPORTED_LOCALES {
+        if strings.EqualFold(locale, l) {
+            return l
+        }
+    }
+    switch locale {
+    case "zh_cn", "zh_sg":
+        return "zh_Hans"
+    case "zh_hk", "zh_tw":
+        return "zh_Hant"
+    }
+    return locale
+}
+
+func isSupported(locale string) bool {
+    for _, l := range SUPPORTED_LOCALES {
+        if strings.EqualFold(locale, l) {
+            return true
+        }
+    }
+    return false
+}
+
+func defaultLocaleForLang(lang string) string {
+    if lang != "" {
+        lang = strings.ToLower(lang)
+        for _, l := range SUPPORTED_LOCALES {
+            if lang == LangOfLocale(l) {
+                return l
+            }
+        }
+    }
+    return ""
+}
+
+func LangOfLocale(locale string) string {
+    if len(locale) < 2 {
+        return ""
+    }
+    return locale[0:2]
+}
diff --git a/tools/cli/go-whisk/wski18n/resources/de_DE.all.json b/tools/cli/go-whisk/wski18n/resources/de_DE.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/en_US.all.json b/tools/cli/go-whisk/wski18n/resources/en_US.all.json
new file mode 100644
index 0000000..e473202
--- /dev/null
+++ b/tools/cli/go-whisk/wski18n/resources/en_US.all.json
@@ -0,0 +1,126 @@
+[
+  {
+    "id": "Unable to add route options '{{.options}}'",
+    "translation": "Unable to add route options: {{.options}}"
+  },
+  {
+    "id": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}",
+    "translation": "Unable to create HTTP request for PUT '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+    "translation": "Unable to create HTTP request for GET '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}",
+    "translation": "Unable to create HTTP request for DELETE '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create HTTP request for POST '{{.route}}': {{.err}}",
+    "translation": "Unable to create HTTP request for POST '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+    "translation": "Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create request URL '{{.url}}': {{.err}}",
+    "translation": "Unable to create request URL '{{.url}}': {{.err}}"
+  },
+  {
+    "id": "Invalid request URL '{{.url}}': {{.err}}",
+    "translation": "Invalid request URL '{{.url}}': {{.err}}"
+  },
+  {
+    "id": "Error encoding request body: {{.err}}",
+    "translation": "Error encoding request body: {{.err}}"
+  },
+  {
+    "id": "Error initializing request: {{.err}}",
+    "translation": "Error initializing request: {{.err}}"
+  },
+  {
+    "id": "Unable to add the HTTP authentication header: {{.err}}",
+    "translation": "Unable to add the HTTP authentication header: {{.err}}"
+  },
+  {
+    "id": "Authorization key is not configured (--auth is required)",
+    "translation": "Authorization key is not configured (--auth is required)"
+  },
+  {
+    "id": "Command failed due to an HTTP failure",
+    "translation": "Command failed due to an HTTP failure"
+  },
+  {
+    "id": "No interface provided; no HTTP response body expected",
+    "translation": "No interface provided; no HTTP response body expected"
+  },
+  {
+    "id": "Command failed due to an internal failure",
+    "translation": "Command failed due to an internal failure"
+  },
+  {
+    "id": "The following application error was received: {{.err}}",
+    "translation": "The following application error was received: {{.err}}"
+  },
+  {
+    "id": "Invalid request URL '{{.url}}': {{.err}}",
+    "translation": "Invalid request URL '{{.url}}': {{.err}}"
+  },
+  {
+    "id": "Unable to add the HTTP authentication header: {{.err}}",
+    "translation": "Unable to add the HTTP authentication header: {{.err}}"
+  },
+  {
+    "id": "Unable to URL parse '{{.version}}': {{.err}}",
+    "translation": "Unable to URL parse '{{.version}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create HTTP request for GET '{{.url}}': {{.err}}",
+    "translation": "Unable to create HTTP request for GET '{{.url}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create HTTP request for GET: {{.err}}",
+    "translation": "Unable to create HTTP request for GET: {{.err}}"
+  },
+  {
+    "id": "Unable to build request URL: {{.err}}",
+    "translation": "Unable to build request URL: {{.err}}"
+  },
+  {
+    "id": "Unable to create GET HTTP request for '{{.route}}': {{.err}}",
+    "translation": "Unable to create GET HTTP request for '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}",
+    "translation": "Unable to create PUT HTTP request for '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}",
+    "translation": "Unable to create DELETE HTTP request for '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to create POST HTTP request for '{{.route}}': {{.err}}",
+    "translation": "Unable to create POST HTTP request for '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\".",
+    "translation": "Internal error. Invalid state option '{{.state}}'. Valid options are \"active\" and \"inactive\"."
+  },
+  {
+    "id": "Unable to parse URL '{{.route}}': {{.err}}",
+    "translation": "Unable to parse URL '{{.route}}': {{.err}}"
+  },
+  {
+    "id": "Unable to process URL query options '{{.options}}': {{.err}}",
+    "translation": "Unable to process URL query options '{{.options}}': {{.err}}"
+  },
+  {
+    "id": "{{.msg}} (code {{.code}})",
+    "translation": "{{.msg}} (code {{.code}})"
+  },
+  {
+    "id": "The connection failed, or timed out. (HTTP status code {{.code}})",
+    "translation": "The connection failed, or timed out. (HTTP status code {{.code}})"
+  }
+]
diff --git a/tools/cli/go-whisk/wski18n/resources/es_ES.all.json b/tools/cli/go-whisk/wski18n/resources/es_ES.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/fr_FR.all.json b/tools/cli/go-whisk/wski18n/resources/fr_FR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/it_IT.all.json b/tools/cli/go-whisk/wski18n/resources/it_IT.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/ja_JA.all.json b/tools/cli/go-whisk/wski18n/resources/ja_JA.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/ko_KR.all.json b/tools/cli/go-whisk/wski18n/resources/ko_KR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/pt_BR.all.json b/tools/cli/go-whisk/wski18n/resources/pt_BR.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/zh_Hans.all.json b/tools/cli/go-whisk/wski18n/resources/zh_Hans.all.json
new file mode 100644
index 0000000..e69de29
diff --git a/tools/cli/go-whisk/wski18n/resources/zh_Hant.all.json b/tools/cli/go-whisk/wski18n/resources/zh_Hant.all.json
new file mode 100644
index 0000000..e69de29

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].