From 50de4cc3264ad69ccef488fc9d126fe1cef13ab2 Mon Sep 17 00:00:00 2001
From: lijianguo <lijianguo1991@outlook.com>
Date: Wed, 12 Mar 2025 17:03:19 +0800
Subject: [PATCH] feat(workflow-cli):replace docopt with cobra

---
 Makefile                                      |    1 +
 cli/cli.go                                    |   18 -
 cmd/root.go                                   |   79 +
 cmd/shortcuts.go                              |   35 -
 cmd/shortcuts_test.go                         |   29 -
 drycc.go                                      |  303 +--
 drycc_test.go                                 |  205 --
 go.mod                                        |   21 +-
 go.sum                                        |   42 +-
 {cmd => internal/commands}/apps.go            |   15 +-
 {cmd => internal/commands}/apps_test.go       |    4 +-
 {cmd => internal/commands}/auth.go            |    4 +-
 {cmd => internal/commands}/auth_test.go       |    2 +-
 {cmd => internal/commands}/autodeploy.go      |    9 +-
 {cmd => internal/commands}/autodeploy_test.go |    2 +-
 {cmd => internal/commands}/autorollback.go    |    9 +-
 .../commands}/autorollback_test.go            |    2 +-
 {cmd => internal/commands}/autoscale.go       |    9 +-
 {cmd => internal/commands}/autoscale_test.go  |    2 +-
 {cmd => internal/commands}/builds.go          |   32 +-
 {cmd => internal/commands}/builds_test.go     |    2 +-
 {cmd => internal/commands}/certs.go           |   15 +-
 {cmd => internal/commands}/certs_test.go      |    2 +-
 cmd/cmd.go => internal/commands/commands.go   |    7 +-
 {cmd => internal/commands}/config.go          |   17 +-
 {cmd => internal/commands}/config_test.go     |    2 +-
 {cmd => internal/commands}/domains.go         |    9 +-
 {cmd => internal/commands}/domains_test.go    |    2 +-
 {cmd => internal/commands}/gateways.go        |    9 +-
 {cmd => internal/commands}/gateways_test.go   |    2 +-
 {cmd => internal/commands}/git.go             |    7 +-
 {cmd => internal/commands}/healthchecks.go    |    9 +-
 .../commands}/healthchecks_test.go            |    2 +-
 {cmd => internal/commands}/keys.go            |    4 +-
 {cmd => internal/commands}/keys_test.go       |    4 +-
 {cmd => internal/commands}/labels.go          |    9 +-
 {cmd => internal/commands}/labels_test.go     |    2 +-
 {cmd => internal/commands}/limits.go          |   11 +-
 {cmd => internal/commands}/limits_test.go     |    2 +-
 {cmd => internal/commands}/perms.go           |   11 +-
 {cmd => internal/commands}/perms_test.go      |    2 +-
 {cmd => internal/commands}/ps.go              |   13 +-
 {cmd => internal/commands}/ps_test.go         |    2 +-
 {cmd => internal/commands}/pts.go             |   13 +-
 {cmd => internal/commands}/pts_test.go        |    2 +-
 {cmd => internal/commands}/registry.go        |    9 +-
 {cmd => internal/commands}/registry_test.go   |    2 +-
 {cmd => internal/commands}/releases.go        |   15 +-
 {cmd => internal/commands}/releases_test.go   |    6 +-
 {cmd => internal/commands}/resources.go       |   19 +-
 {cmd => internal/commands}/resources_test.go  |    2 +-
 {cmd => internal/commands}/routes.go          |   17 +-
 {cmd => internal/commands}/routes_test.go     |    2 +-
 {cmd => internal/commands}/routing.go         |    9 +-
 {cmd => internal/commands}/routing_test.go    |    2 +-
 {cmd => internal/commands}/services.go        |    9 +-
 {cmd => internal/commands}/tags.go            |   28 +-
 {cmd => internal/commands}/tags_test.go       |   16 +-
 {cmd => internal/commands}/timeouts.go        |    9 +-
 {cmd => internal/commands}/timeouts_test.go   |    2 +-
 {cmd => internal/commands}/tls.go             |   15 +-
 {cmd => internal/commands}/tls_test.go        |    2 +-
 {cmd => internal/commands}/token.go           |    4 +-
 {cmd => internal/commands}/token_test.go      |    2 +-
 {cmd => internal/commands}/update.go          |    6 +-
 {cmd => internal/commands}/users.go           |    4 +-
 {cmd => internal/commands}/users_test.go      |    2 +-
 {cmd => internal/commands}/utils.go           |   30 +-
 {cmd => internal/commands}/utils_test.go      |   41 +-
 {cmd => internal/commands}/version.go         |    4 +-
 {cmd => internal/commands}/version_test.go    |    2 +-
 {cmd => internal/commands}/volumes.go         |   23 +-
 {cmd => internal/commands}/volumes_test.go    |    2 +-
 internal/completion/completion.go             |  876 ++++++++
 internal/parser/apps.go                       |  222 ++
 internal/parser/auth.go                       |   80 +
 internal/parser/autodeploy.go                 |   65 +
 internal/parser/autorollback.go               |   64 +
 internal/parser/autoscale.go                  |  109 +
 internal/parser/builds.go                     |  114 ++
 internal/parser/certs.go                      |  170 ++
 internal/parser/config.go                     |  257 +++
 internal/parser/domains.go                    |   83 +
 internal/parser/flags.go                      |    5 +
 internal/parser/gateways.go                   |  111 +
 internal/parser/git.go                        |   58 +
 internal/parser/healthchecks.go               |  263 +++
 internal/parser/keys.go                       |   90 +
 internal/parser/labels.go                     |   99 +
 internal/parser/limits.go                     |  157 ++
 internal/parser/perms.go                      |  136 ++
 internal/parser/ps.go                         |  163 ++
 internal/parser/pts.go                        |  164 ++
 internal/parser/registry.go                   |  111 +
 internal/parser/releases.go                   |  170 ++
 internal/parser/resources.go                  |  276 +++
 internal/parser/routes.go                     |  257 +++
 internal/parser/routing.go                    |   64 +
 internal/parser/services.go                   |  121 ++
 internal/parser/shortcuts.go                  |   87 +
 internal/parser/tags.go                       |  136 ++
 internal/parser/timeouts.go                   |  100 +
 internal/parser/tls.go                        |  132 ++
 internal/parser/tokens.go                     |  106 +
 internal/parser/update.go                     |   22 +
 internal/parser/users.go                      |   91 +
 internal/parser/version.go                    |   24 +
 internal/parser/volumes.go                    |  273 +++
 internal/template/template.go                 |   42 +
 internal/utils/utils.go                       |   25 +
 internal/utils/utils_test.go                  |   48 +
 parser/apps.go                                |  287 ---
 parser/apps_test.go                           |  118 --
 parser/auth.go                                |  106 -
 parser/auth_test.go                           |   70 -
 parser/autodeploy.go                          |   98 -
 parser/autodeploy_test.go                     |   74 -
 parser/autorollback.go                        |   98 -
 parser/autorollback_test.go                   |   74 -
 parser/autoscale.go                           |  123 --
 parser/autoscale_test.go                      |   74 -
 parser/builds.go                              |  156 --
 parser/builds_test.go                         |   71 -
 parser/certs.go                               |  208 --
 parser/certs_test.go                          |   98 -
 parser/config.go                              |  292 ---
 parser/config_test.go                         |  106 -
 parser/domains.go                             |  126 --
 parser/domains_test.go                        |   74 -
 parser/gateways.go                            |  134 --
 parser/gateways_test.go                       |   74 -
 parser/git.go                                 |   81 -
 parser/git_test.go                            |   62 -
 parser/healthchecks.go                        |  302 ---
 parser/healthchecks_test.go                   |   95 -
 parser/keys.go                                |  108 -
 parser/keys_test.go                           |   74 -
 parser/labels.go                              |  114 --
 parser/labels_test.go                         |   74 -
 parser/limits.go                              |  196 --
 parser/limits_test.go                         |   92 -
 parser/parser_test.go                         |   42 -
 parser/perms.go                               |  152 --
 parser/perms_test.go                          |   82 -
 parser/ps.go                                  |  190 --
 parser/ps_test.go                             |   93 -
 parser/pts.go                                 |  171 --
 parser/pts_test.go                            |   90 -
 parser/registry.go                            |  126 --
 parser/registry_test.go                       |   74 -
 parser/releases.go                            |  190 --
 parser/releases_test.go                       |   86 -
 parser/resources.go                           |  335 ---
 parser/resources_test.go                      |  122 --
 parser/routes.go                              |  286 ---
 parser/routes_test.go                         |  105 -
 parser/routing.go                             |   98 -
 parser/routing_test.go                        |   74 -
 parser/services.go                            |  132 --
 parser/shortcuts.go                           |   50 -
 parser/shortcuts_test.go                      |   58 -
 parser/tags.go                                |  136 --
 parser/tags_test.go                           |   74 -
 parser/timeouts.go                            |  123 --
 parser/timeouts_test.go                       |   78 -
 parser/tls.go                                 |  183 --
 parser/tls_test.go                            |   90 -
 parser/tokens.go                              |  113 -
 parser/tokens_test.go                         |   72 -
 parser/update.go                              |   24 -
 parser/update_test.go                         |   54 -
 parser/users.go                               |  108 -
 parser/users_test.go                          |   74 -
 parser/utils.go                               |   60 -
 parser/utils_test.go                          |   74 -
 parser/version.go                             |   25 -
 parser/version_test.go                        |   54 -
 parser/volumes.go                             |  309 ---
 parser/volumes_test.go                        |  110 -
 pkg/i18n/i18n.go                              |  209 ++
 pkg/i18n/i18n_test.go                         |  292 +++
 .../drycc/en_US/LC_MESSAGES/cli.mo            |  Bin 0 -> 764 bytes
 .../drycc/en_US/LC_MESSAGES/cli.po            | 1727 ++++++++++++++++
 pkg/i18n/translations/drycc/template.pot      | 1717 ++++++++++++++++
 .../drycc/zh_CN/LC_MESSAGES/cli.mo            |  Bin 0 -> 35245 bytes
 .../drycc/zh_CN/LC_MESSAGES/cli.po            | 1818 +++++++++++++++++
 .../test/en_US/LC_MESSAGES/cli.mo             |  Bin 0 -> 563 bytes
 .../test/en_US/LC_MESSAGES/cli.po             |   28 +
 .../test/zh_CN/LC_MESSAGES/cli.mo             |  Bin 0 -> 563 bytes
 .../test/zh_CN/LC_MESSAGES/cli.po             |   28 +
 {settings => pkg/settings}/home_unix.go       |    0
 {settings => pkg/settings}/home_unix_test.go  |    0
 {settings => pkg/settings}/home_windows.go    |    0
 .../settings}/home_windows_test.go            |    0
 {settings => pkg/settings}/settings.go        |   18 +-
 {settings => pkg/settings}/settings_test.go   |    0
 {settings => pkg/settings}/utils.go           |   16 +-
 {settings => pkg/settings}/utils_test.go      |    2 +-
 pkg/testutil/testutil.go                      |    2 +-
 scripts/update-translations.sh                |  132 ++
 200 files changed, 11740 insertions(+), 8863 deletions(-)
 delete mode 100644 cli/cli.go
 create mode 100644 cmd/root.go
 delete mode 100644 cmd/shortcuts.go
 delete mode 100644 cmd/shortcuts_test.go
 delete mode 100644 drycc_test.go
 rename {cmd => internal/commands}/apps.go (95%)
 rename {cmd => internal/commands}/apps_test.go (99%)
 rename {cmd => internal/commands}/auth.go (96%)
 rename {cmd => internal/commands}/auth_test.go (99%)
 rename {cmd => internal/commands}/autodeploy.go (85%)
 rename {cmd => internal/commands}/autodeploy_test.go (99%)
 rename {cmd => internal/commands}/autorollback.go (85%)
 rename {cmd => internal/commands}/autorollback_test.go (99%)
 rename {cmd => internal/commands}/autoscale.go (88%)
 rename {cmd => internal/commands}/autoscale_test.go (99%)
 rename {cmd => internal/commands}/builds.go (91%)
 rename {cmd => internal/commands}/builds_test.go (99%)
 rename {cmd => internal/commands}/certs.go (91%)
 rename {cmd => internal/commands}/certs_test.go (99%)
 rename cmd/cmd.go => internal/commands/commands.go (97%)
 rename {cmd => internal/commands}/config.go (95%)
 rename {cmd => internal/commands}/config_test.go (99%)
 rename {cmd => internal/commands}/domains.go (86%)
 rename {cmd => internal/commands}/domains_test.go (99%)
 rename {cmd => internal/commands}/gateways.go (89%)
 rename {cmd => internal/commands}/gateways_test.go (99%)
 rename {cmd => internal/commands}/git.go (90%)
 rename {cmd => internal/commands}/healthchecks.go (94%)
 rename {cmd => internal/commands}/healthchecks_test.go (99%)
 rename {cmd => internal/commands}/keys.go (98%)
 rename {cmd => internal/commands}/keys_test.go (99%)
 rename {cmd => internal/commands}/labels.go (90%)
 rename {cmd => internal/commands}/labels_test.go (99%)
 rename {cmd => internal/commands}/limits.go (94%)
 rename {cmd => internal/commands}/limits_test.go (99%)
 rename {cmd => internal/commands}/perms.go (85%)
 rename {cmd => internal/commands}/perms_test.go (99%)
 rename {cmd => internal/commands}/ps.go (96%)
 rename {cmd => internal/commands}/ps_test.go (99%)
 rename {cmd => internal/commands}/pts.go (94%)
 rename {cmd => internal/commands}/pts_test.go (99%)
 rename {cmd => internal/commands}/registry.go (90%)
 rename {cmd => internal/commands}/registry_test.go (99%)
 rename {cmd => internal/commands}/releases.go (90%)
 rename {cmd => internal/commands}/releases_test.go (98%)
 rename {cmd => internal/commands}/resources.go (93%)
 rename {cmd => internal/commands}/resources_test.go (99%)
 rename {cmd => internal/commands}/routes.go (89%)
 rename {cmd => internal/commands}/routes_test.go (99%)
 rename {cmd => internal/commands}/routing.go (85%)
 rename {cmd => internal/commands}/routing_test.go (99%)
 rename {cmd => internal/commands}/services.go (90%)
 rename {cmd => internal/commands}/tags.go (81%)
 rename {cmd => internal/commands}/tags_test.go (95%)
 rename {cmd => internal/commands}/timeouts.go (91%)
 rename {cmd => internal/commands}/timeouts_test.go (99%)
 rename {cmd => internal/commands}/tls.go (89%)
 rename {cmd => internal/commands}/tls_test.go (99%)
 rename {cmd => internal/commands}/token.go (98%)
 rename {cmd => internal/commands}/token_test.go (99%)
 rename {cmd => internal/commands}/update.go (92%)
 rename {cmd => internal/commands}/users.go (96%)
 rename {cmd => internal/commands}/users_test.go (99%)
 rename {cmd => internal/commands}/utils.go (90%)
 rename {cmd => internal/commands}/utils_test.go (56%)
 rename {cmd => internal/commands}/version.go (91%)
 rename {cmd => internal/commands}/version_test.go (98%)
 rename {cmd => internal/commands}/volumes.go (94%)
 rename {cmd => internal/commands}/volumes_test.go (99%)
 create mode 100644 internal/completion/completion.go
 create mode 100644 internal/parser/apps.go
 create mode 100644 internal/parser/auth.go
 create mode 100644 internal/parser/autodeploy.go
 create mode 100644 internal/parser/autorollback.go
 create mode 100644 internal/parser/autoscale.go
 create mode 100644 internal/parser/builds.go
 create mode 100644 internal/parser/certs.go
 create mode 100644 internal/parser/config.go
 create mode 100644 internal/parser/domains.go
 create mode 100644 internal/parser/flags.go
 create mode 100644 internal/parser/gateways.go
 create mode 100644 internal/parser/git.go
 create mode 100644 internal/parser/healthchecks.go
 create mode 100644 internal/parser/keys.go
 create mode 100644 internal/parser/labels.go
 create mode 100644 internal/parser/limits.go
 create mode 100644 internal/parser/perms.go
 create mode 100644 internal/parser/ps.go
 create mode 100644 internal/parser/pts.go
 create mode 100644 internal/parser/registry.go
 create mode 100644 internal/parser/releases.go
 create mode 100644 internal/parser/resources.go
 create mode 100644 internal/parser/routes.go
 create mode 100644 internal/parser/routing.go
 create mode 100644 internal/parser/services.go
 create mode 100644 internal/parser/shortcuts.go
 create mode 100644 internal/parser/tags.go
 create mode 100644 internal/parser/timeouts.go
 create mode 100644 internal/parser/tls.go
 create mode 100644 internal/parser/tokens.go
 create mode 100644 internal/parser/update.go
 create mode 100644 internal/parser/users.go
 create mode 100644 internal/parser/version.go
 create mode 100644 internal/parser/volumes.go
 create mode 100644 internal/template/template.go
 create mode 100644 internal/utils/utils.go
 create mode 100644 internal/utils/utils_test.go
 delete mode 100644 parser/apps.go
 delete mode 100644 parser/apps_test.go
 delete mode 100644 parser/auth.go
 delete mode 100644 parser/auth_test.go
 delete mode 100644 parser/autodeploy.go
 delete mode 100644 parser/autodeploy_test.go
 delete mode 100644 parser/autorollback.go
 delete mode 100644 parser/autorollback_test.go
 delete mode 100644 parser/autoscale.go
 delete mode 100644 parser/autoscale_test.go
 delete mode 100644 parser/builds.go
 delete mode 100644 parser/builds_test.go
 delete mode 100644 parser/certs.go
 delete mode 100644 parser/certs_test.go
 delete mode 100644 parser/config.go
 delete mode 100644 parser/config_test.go
 delete mode 100644 parser/domains.go
 delete mode 100644 parser/domains_test.go
 delete mode 100644 parser/gateways.go
 delete mode 100644 parser/gateways_test.go
 delete mode 100644 parser/git.go
 delete mode 100644 parser/git_test.go
 delete mode 100644 parser/healthchecks.go
 delete mode 100644 parser/healthchecks_test.go
 delete mode 100644 parser/keys.go
 delete mode 100644 parser/keys_test.go
 delete mode 100644 parser/labels.go
 delete mode 100644 parser/labels_test.go
 delete mode 100644 parser/limits.go
 delete mode 100644 parser/limits_test.go
 delete mode 100644 parser/parser_test.go
 delete mode 100644 parser/perms.go
 delete mode 100644 parser/perms_test.go
 delete mode 100644 parser/ps.go
 delete mode 100644 parser/ps_test.go
 delete mode 100644 parser/pts.go
 delete mode 100644 parser/pts_test.go
 delete mode 100644 parser/registry.go
 delete mode 100644 parser/registry_test.go
 delete mode 100644 parser/releases.go
 delete mode 100644 parser/releases_test.go
 delete mode 100644 parser/resources.go
 delete mode 100644 parser/resources_test.go
 delete mode 100644 parser/routes.go
 delete mode 100644 parser/routes_test.go
 delete mode 100644 parser/routing.go
 delete mode 100644 parser/routing_test.go
 delete mode 100644 parser/services.go
 delete mode 100644 parser/shortcuts.go
 delete mode 100644 parser/shortcuts_test.go
 delete mode 100644 parser/tags.go
 delete mode 100644 parser/tags_test.go
 delete mode 100644 parser/timeouts.go
 delete mode 100644 parser/timeouts_test.go
 delete mode 100644 parser/tls.go
 delete mode 100644 parser/tls_test.go
 delete mode 100644 parser/tokens.go
 delete mode 100644 parser/tokens_test.go
 delete mode 100644 parser/update.go
 delete mode 100644 parser/update_test.go
 delete mode 100644 parser/users.go
 delete mode 100644 parser/users_test.go
 delete mode 100644 parser/utils.go
 delete mode 100644 parser/utils_test.go
 delete mode 100644 parser/version.go
 delete mode 100644 parser/version_test.go
 delete mode 100644 parser/volumes.go
 delete mode 100644 parser/volumes_test.go
 create mode 100644 pkg/i18n/i18n.go
 create mode 100644 pkg/i18n/i18n_test.go
 create mode 100644 pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.mo
 create mode 100644 pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
 create mode 100644 pkg/i18n/translations/drycc/template.pot
 create mode 100644 pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.mo
 create mode 100644 pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
 create mode 100644 pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.mo
 create mode 100644 pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.po
 create mode 100644 pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.mo
 create mode 100644 pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.po
 rename {settings => pkg/settings}/home_unix.go (100%)
 rename {settings => pkg/settings}/home_unix_test.go (100%)
 rename {settings => pkg/settings}/home_windows.go (100%)
 rename {settings => pkg/settings}/home_windows_test.go (100%)
 rename {settings => pkg/settings}/settings.go (88%)
 rename {settings => pkg/settings}/settings_test.go (100%)
 rename {settings => pkg/settings}/utils.go (58%)
 rename {settings => pkg/settings}/utils_test.go (92%)
 create mode 100755 scripts/update-translations.sh

diff --git a/Makefile b/Makefile
index faefae28b..dc5dc7250 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ bootstrap:
 
 # This is supposed to be run within a container
 build:
+	${DEV_ENV_CMD} scripts/update-translations.sh -g
 	${DEV_ENV_CMD} scripts/build ${VERSION}
 
 test-style:
diff --git a/cli/cli.go b/cli/cli.go
deleted file mode 100644
index 11274e42d..000000000
--- a/cli/cli.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package cli
-
-// Shortcuts is a map of all the shortcuts supported by the CLI
-var Shortcuts = map[string]string{
-	"create":   "apps:create",
-	"destroy":  "apps:destroy",
-	"exec":     "ps:exec",
-	"info":     "apps:info",
-	"login":    "auth:login",
-	"logout":   "auth:logout",
-	"logs":     "ps:logs",
-	"open":     "apps:open",
-	"pull":     "builds:create",
-	"rollback": "releases:rollback",
-	"run":      "apps:run",
-	"scale":    "pts:scale",
-	"whoami":   "auth:whoami",
-}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 000000000..cb2b65faa
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,79 @@
+/*
+Copyright © 2025 NAME HERE <EMAIL ADDRESS>
+*/
+package cmd
+
+import (
+	"os"
+	"time"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/parser"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewDryccCommand() *cobra.Command {
+	var flags struct {
+		config  string
+		version bool
+		help    bool
+	}
+
+	var cmdr commands.DryccCmd
+
+	rootCmd := &cobra.Command{
+		Use:   "drycc",
+		Short: i18n.T("The Drycc command-line client issues API calls to a Drycc controller"),
+		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+			cmdr = commands.DryccCmd{ConfigFile: flags.config, WOut: os.Stdout, WErr: os.Stderr, WIn: os.Stdin, Location: time.Local}
+		},
+	}
+
+	rootCmd.PersistentFlags().StringVarP(&flags.config, "config", "c", "~/.drycc/client.json", i18n.T("Path to configuration file"))
+	rootCmd.PersistentFlags().BoolVarP(&flags.help, "help", "h", false, i18n.T("Display help information"))
+
+	rootCmd.AddCommand(parser.NewAppsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewAuthCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewAutodeployCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewAutorollbackCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewAutoscaleCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewBuildsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewCertsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewConfigCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewDomainsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewGatewaysCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewGitCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewHealthchecksCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewKeysCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewLabelsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewLimitsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewPermsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewPsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewPtsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewRegistryCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewReleasesCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewResourcesCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewRoutesCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewRoutingCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewServicesCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewTagsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewTimeoutsCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewTLSCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewTokensCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewUpdateCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewUsersCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewVolumesCommand(&cmdr))
+	rootCmd.AddCommand(parser.NewVersionCommand(&cmdr))
+	// shortcuts
+	rootCmd.AddGroup(&cobra.Group{ID: "shortcut", Title: i18n.T("Shortcut Commands:")})
+	for _, shortcuts := range parser.SupportedShortcuts {
+		for _, shortcut := range shortcuts.Create(&cmdr) {
+			shortcut.GroupID = "shortcut"
+			rootCmd.AddCommand(shortcut)
+		}
+	}
+	rootCmd.SilenceUsage = true
+
+	return rootCmd
+}
diff --git a/cmd/shortcuts.go b/cmd/shortcuts.go
deleted file mode 100644
index b63f03a60..000000000
--- a/cmd/shortcuts.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package cmd
-
-import (
-	"fmt"
-	"sort"
-
-	"github.com/drycc/workflow-cli/cli"
-)
-
-// ShortcutsList displays all relevant shortcuts for the CLI.
-func (d *DryccCmd) ShortcutsList() error {
-	d.Println(sortShortcuts())
-
-	return nil
-}
-
-func sortShortcuts() string {
-	var (
-		strBuilder string
-		keys       []string
-	)
-
-	// NOTE(bacongobbler): go does not guarantee an iteration order when iterating over a map,
-	// so to work around this we can sort the keys and iterate using the key array
-	for k := range cli.Shortcuts {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
-
-	for _, k := range keys {
-		strBuilder += fmt.Sprintf("%s -> %s\n", k, cli.Shortcuts[k])
-	}
-
-	return strBuilder
-}
diff --git a/cmd/shortcuts_test.go b/cmd/shortcuts_test.go
deleted file mode 100644
index 1e17edbf1..000000000
--- a/cmd/shortcuts_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package cmd
-
-import (
-	"testing"
-)
-
-func TestShortcutsList(t *testing.T) {
-	t.Parallel()
-
-	expected := `create -> apps:create
-destroy -> apps:destroy
-exec -> ps:exec
-info -> apps:info
-login -> auth:login
-logout -> auth:logout
-logs -> ps:logs
-open -> apps:open
-pull -> builds:create
-rollback -> releases:rollback
-run -> apps:run
-scale -> pts:scale
-whoami -> auth:whoami
-`
-	actual := sortShortcuts()
-	if actual != expected {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-
-}
diff --git a/drycc.go b/drycc.go
index 3e51b28d1..c2392f9b3 100644
--- a/drycc.go
+++ b/drycc.go
@@ -1,305 +1,20 @@
+/*
+Copyright © 2025 NAME HERE <EMAIL ADDRESS>
+*/
 package main
 
 import (
-	"fmt"
-	"io"
+	"context"
 	"os"
-	"os/exec"
-	"strings"
-	"syscall"
-	"time"
 
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cli"
 	"github.com/drycc/workflow-cli/cmd"
-	"github.com/drycc/workflow-cli/parser"
 )
 
-const extensionPrefix = "drycc-"
-
-// main exits with the return value of Command(os.Args[1:]), deferring all logic to
-// a func we can test.
 func main() {
-	os.Exit(Command(os.Args[1:], os.Stdout, os.Stderr, os.Stdin))
-}
-
-// Command routes drycc commands to their proper parser.
-func Command(argv []string, wOut io.Writer, wErr io.Writer, wIn io.Reader) int {
-	usage := `
-The Drycc command-line client issues API calls to a Drycc controller.
-
-Usage: drycc <command> [<args>...]
-
-Options:
-  -h --help
-    display help information
-  -v --version
-    display client version
-  -c --config=<config>
-    path to configuration file. Equivalent to
-    setting $DRYCC_PROFILE. Defaults to ~/.drycc/config.json.
-    If value is not a filepath, will assume location ~/.drycc/client.json
-
-Auth commands, use 'drycc help auth' to learn more:
-
-  login         login to a controller
-  logout        logout from the current controller
-
-Subcommands, use 'drycc help [subcommand]' to learn more:
-
-  apps          manage applications used to provide services
-  autodeploy    manage autodeploy if or not for applications 
-  autorollback  manage autorollback if or not for applications 
-  autoscale     manage autoscale for applications
-  builds        manage builds created using 'git push'
-  certs         manage SSL endpoints for an app
-  config        manage environment variables that define app config
-  domains       manage and assign domain names to your applications
-  gateways      manage gateways for your applications
-  git           manage git for applications
-  healthchecks  manage healthchecks for applications
-  keys          manage ssh keys used for 'git push' deployments
-  labels        manage labels of application
-  limits        manage resource limits for your application
-  perms         manage permissions for applications
-  ps            manage processes inside an app
-  pts           manage process types inside an app
-  registry      manage private registry information for your application
-  resources     manage resources for your applications 
-  releases      manage releases of an application
-  routes        manage routes for your applications 
-  routing       manage routability of an application
-  services      manage services for your applications
-  tags          manage tags for application containers
-  timeouts      manage pods termination grace period 
-  tls           manage TLS settings for applications
-  tokens        manage user tokens
-  update        update workflow cli to latest release
-  users         manage registered users
-  version       display client version
-  volumes       manage volumes for your applications
-
-Shortcut commands, use 'drycc shortcuts' to see all:
-
-  create        create a new application
-  destroy       destroy an application
-  exec          execute a command in a container
-  info          view information about the current app
-  logs          print the logs for a container
-  open          open a URL to the app in a browser
-  pull          imports an image and deploys as a new release
-  run           run a command in an ephemeral app container
-  scale         scale processes by type (web=2 worker=1)
-
-Use 'git push drycc main' to deploy to an application.
-`
-	// Reorganize some command line flags and commands.
-	command, argv := parseArgs(argv)
-	// Give docopt an optional final false arg so it doesn't call os.Exit().
-	_, err := docopt.ParseArgs(usage, []string{command}, "")
-
-	if err != nil {
-		fmt.Fprintln(wErr, err)
-		return 1
-	}
-
-	if len(argv) == 0 {
-		fmt.Fprintln(wErr, "Usage: drycc <command> [<args>...]")
-		return 1
-	}
-
-	configFlag := getConfigFlag(argv)
-	// Don't pass down config flag to parser because it isn't defined there.
-	argv = removeConfigFlag(argv)
-	cmdr := cmd.DryccCmd{ConfigFile: configFlag, WOut: wOut, WErr: wErr, WIn: wIn, Location: time.Local}
-
-	// Dispatch the command, passing the argv through so subcommands can
-	// re-parse it according to their usage strings.
-	switch command {
-	case "apps":
-		err = parser.Apps(argv, &cmdr)
-	case "auth":
-		err = parser.Auth(argv, &cmdr)
-	case "autodeploy":
-		err = parser.Autodeploy(argv, &cmdr)
-	case "autorollback":
-		err = parser.Autorollback(argv, &cmdr)
-	case "autoscale":
-		err = parser.Autoscale(argv, &cmdr)
-	case "builds":
-		err = parser.Builds(argv, &cmdr)
-	case "certs":
-		err = parser.Certs(argv, &cmdr)
-	case "config":
-		err = parser.Config(argv, &cmdr)
-	case "domains":
-		err = parser.Domains(argv, &cmdr)
-	case "services":
-		err = parser.Services(argv, &cmdr)
-	case "gateways":
-		err = parser.Gateways(argv, &cmdr)
-	case "routes":
-		err = parser.Routes(argv, &cmdr)
-	case "git":
-		err = parser.Git(argv, &cmdr)
-	case "healthchecks":
-		err = parser.Healthchecks(argv, &cmdr)
-	case "help":
-		fmt.Fprint(os.Stdout, usage)
-		return 0
-	case "keys":
-		err = parser.Keys(argv, &cmdr)
-	case "labels":
-		err = parser.Labels(argv, &cmdr)
-	case "limits":
-		err = parser.Limits(argv, &cmdr)
-	case "timeouts":
-		err = parser.Timeouts(argv, &cmdr)
-	case "perms":
-		err = parser.Perms(argv, &cmdr)
-	case "ps":
-		err = parser.Ps(argv, &cmdr)
-	case "pts":
-		err = parser.Pts(argv, &cmdr)
-	case "registry":
-		err = parser.Registry(argv, &cmdr)
-	case "releases":
-		err = parser.Releases(argv, &cmdr)
-	case "routing":
-		err = parser.Routing(argv, &cmdr)
-	case "shortcuts":
-		err = parser.Shortcuts(argv, &cmdr)
-	case "tags":
-		err = parser.Tags(argv, &cmdr)
-	case "tls":
-		err = parser.TLS(argv, &cmdr)
-	case "update":
-		err = parser.Update(argv, &cmdr)
-	case "users":
-		err = parser.Users(argv, &cmdr)
-	case "tokens":
-		err = parser.Tokens(argv, &cmdr)
-	case "version":
-		err = parser.Version(argv, &cmdr)
-	case "volumes":
-		err = parser.Volumes(argv, &cmdr)
-	case "resources":
-		err = parser.Resources(argv, &cmdr)
-	default:
-		env := os.Environ()
-
-		binary, err := exec.LookPath(extensionPrefix + command)
-		if err != nil {
-			parser.PrintUsage(&cmdr)
-			return 1
-		}
-
-		cmdArgv := prepareCmdArgs(command, argv)
-
-		err = syscall.Exec(binary, cmdArgv, env)
-		if err != nil {
-			parser.PrintUsage(&cmdr)
-			return 1
-		}
-	}
-	if err != nil {
-		fmt.Fprintf(wErr, "Error: %v\n", err)
-		return 1
-	}
-	return 0
-}
-
-func removeConfigFlag(argv []string) []string {
-	var kept []string
-	for i, arg := range argv {
-		// -- /bin/sh -c --config  condition
-		if arg == "--" {
-			kept = append(kept, argv[i:]...)
-			break
-		}
-
-		if arg == "-c" || strings.HasPrefix(arg, "--config=") {
-			continue
-			// If the previous option is -c, remove the argument as well
-		} else if i != 0 && argv[i-1] == "-c" {
-			continue
-		}
-
-		kept = append(kept, arg)
-	}
-
-	return kept
-}
-
-func getConfigFlag(argv []string) string {
-	for i, arg := range argv {
-		// -- /bin/sh -c/ --config=  condition
-		if arg == "--" {
-			return ""
-		}
-		if strings.HasPrefix(arg, "--config=") {
-			return strings.TrimPrefix(arg, "--config=")
-		} else if i != 0 && argv[i-1] == "-c" {
-			return arg
-		}
-	}
-
-	return ""
-}
-
-// parseArgs returns the provided args with "--help" as the last arg if need be,
-// expands shortcuts and formats commands to be properly routed.
-func parseArgs(argv []string) (string, []string) {
-	if len(argv) == 1 {
-		if argv[0] == "--help" || argv[0] == "-h" {
-			// rearrange "drycc --help" as "drycc help"
-			argv[0] = "help"
-		} else if argv[0] == "--version" || argv[0] == "-v" {
-			// rearrange "drycc --version" as "drycc version"
-			argv[0] = "version"
-		}
-	}
-
-	if len(argv) > 1 {
-		// Rearrange "drycc help <command>" to "drycc <command> --help".
-		if argv[0] == "help" || argv[0] == "--help" || argv[0] == "-h" {
-			argv = append(argv[1:], "--help")
-		}
-	}
-
-	if len(argv) > 0 {
-		argv[0] = replaceShortcut(argv[0])
-
-		index := strings.Index(argv[0], ":")
-
-		if index != -1 {
-			command := argv[0]
-			return command[:index], argv
-		}
-
-		return argv[0], argv
+	rootCmd := cmd.NewDryccCommand()
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	if err := rootCmd.ExecuteContext(ctx); err != nil {
+		os.Exit(1)
 	}
-
-	return "", argv
-}
-
-// split original command and pass its first element in arguments
-func prepareCmdArgs(command string, argv []string) []string {
-	cmdArgv := []string{extensionPrefix + command}
-	cmdSplit := strings.Split(argv[0], command+":")
-
-	if len(cmdSplit) > 1 {
-		cmdArgv = append(cmdArgv, cmdSplit[1])
-	}
-
-	return append(cmdArgv, argv[1:]...)
-}
-
-func replaceShortcut(command string) string {
-	expandedCommand := cli.Shortcuts[command]
-	if expandedCommand == "" {
-		return command
-	}
-
-	return expandedCommand
 }
diff --git a/drycc_test.go b/drycc_test.go
deleted file mode 100644
index 0e03150e5..000000000
--- a/drycc_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package main
-
-import (
-	"reflect"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestHelpReformatting(t *testing.T) {
-	t.Parallel()
-
-	tests := []string{"--help", "-h", "help"}
-	expected := "help"
-
-	for _, test := range tests {
-		actual, argv := parseArgs([]string{test})
-
-		if actual != expected {
-			t.Errorf("Expected %s, Got %s", expected, actual)
-		}
-
-		if len(argv) != 1 {
-			t.Errorf("Expected length of 1, Got %d", len(argv))
-		}
-	}
-}
-
-func TestHelpReformattingWithCommand(t *testing.T) {
-	t.Parallel()
-
-	tests := []string{"--help", "-h", "help"}
-	expected := "test"
-	expectedArgv := []string{"test", "--help"}
-
-	for _, test := range tests {
-		actual, argv := parseArgs([]string{test, "test"})
-
-		if actual != expected {
-			t.Errorf("Expected %s, Got %s", expected, actual)
-		}
-
-		if !reflect.DeepEqual(expectedArgv, argv) {
-			t.Errorf("Expected %v, Got %v", expectedArgv, argv)
-		}
-	}
-}
-
-func TestCommandSplitting(t *testing.T) {
-	t.Parallel()
-
-	expected := "apps"
-	expectedArgv := []string{"apps:create", "test", "foo"}
-
-	actual, argv := parseArgs(expectedArgv)
-
-	if actual != expected {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-
-	if !reflect.DeepEqual(expectedArgv, argv) {
-		t.Errorf("Expected %v, Got %v", expectedArgv, argv)
-	}
-}
-
-func TestTopLevelCommandArgsPreparing(t *testing.T) {
-	t.Parallel()
-
-	command := "ssh"
-	argv := []string{"ssh"}
-	expected := []string{"drycc-ssh"}
-	actual := prepareCmdArgs(command, argv)
-
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Expected %v, Got %v", expected, actual)
-	}
-}
-
-func TestCommandWithParameterArgsPreparing(t *testing.T) {
-	t.Parallel()
-
-	command := "ssh --help"
-	argv := []string{"ssh --help"}
-	expected := []string{"drycc-ssh --help"}
-	actual := prepareCmdArgs(command, argv)
-
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Expected %v, Got %v", expected, actual)
-	}
-}
-
-func TestReplaceShortcutRepalce(t *testing.T) {
-	t.Parallel()
-
-	expected := "apps:create"
-	actual := replaceShortcut("create")
-
-	if expected != actual {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-}
-
-func TestReplaceShortcutUnchanged(t *testing.T) {
-	t.Parallel()
-
-	expected := "users:list"
-	actual := replaceShortcut(expected)
-
-	if expected != actual {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-}
-
-func TestGetConfigFlag(t *testing.T) {
-	t.Parallel()
-
-	expected := "the-config-flag"
-	argv := []string{
-		"lorem",
-		"ipsum",
-		"--config=" + expected,
-	}
-	actual := getConfigFlag(argv)
-	assert.Equal(t, actual, expected, "config-flag")
-
-	argv = []string{
-		"lorem",
-		"ipsum",
-		"-c",
-		expected,
-	}
-	actual = getConfigFlag(argv)
-	assert.Equal(t, actual, expected, "config-flag")
-
-	expected = ""
-	argv = []string{
-		"--",
-		"ipsum",
-		"--config=config",
-	}
-	actual = getConfigFlag(argv)
-	assert.Equal(t, actual, expected, "config-flag")
-
-	argv = []string{
-		"--",
-		"ipsum",
-		"-c",
-		"config",
-	}
-	actual = getConfigFlag(argv)
-	assert.Equal(t, actual, expected, "config-flag")
-}
-
-func TestRemoveConfigFlag(t *testing.T) {
-	t.Parallel()
-	expected := []string{
-		"lorem",
-		"ipsum",
-	}
-
-	argv := []string{
-		"lorem",
-		"ipsum",
-		"--config=the-config-flag",
-	}
-	actual := removeConfigFlag(argv)
-	assert.Equal(t, actual, expected, "args")
-
-	argv = []string{
-		"lorem",
-		"ipsum",
-		"-c",
-		"the-config-flag",
-	}
-	actual = removeConfigFlag(argv)
-	assert.Equal(t, actual, expected, "args")
-
-	expected = []string{
-		"--",
-		"sh",
-		"-c",
-		"the-config-flag",
-	}
-	argv = []string{
-		"--",
-		"sh",
-		"-c",
-		"the-config-flag",
-	}
-	actual = removeConfigFlag(argv)
-	assert.Equal(t, actual, expected, "args")
-
-	expected = []string{
-		"--",
-		"sh",
-		"--config=the-config-flag",
-	}
-	argv = []string{
-		"--",
-		"sh",
-		"--config=the-config-flag",
-	}
-	actual = removeConfigFlag(argv)
-	assert.Equal(t, actual, expected, "args")
-}
diff --git a/go.mod b/go.mod
index d6a42f9ef..3926f734a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,29 +3,34 @@ module github.com/drycc/workflow-cli
 go 1.24
 
 require (
+	github.com/chai2010/gettext-go v1.0.3
 	github.com/containerd/console v1.0.4
-	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
-	github.com/drycc/controller-sdk-go v0.0.0-20250403055121-5e021ebe1910
-	github.com/drycc/pkg v0.0.0-20250324060552-65752139ab2f
+	github.com/drycc/controller-sdk-go v0.0.0-20250326034724-1dafccaadf6b
+	github.com/drycc/pkg v0.0.0-20241223062248-04810c5faa4b
 	github.com/minio/selfupdate v0.6.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/schollz/progressbar/v3 v3.18.0
+	github.com/spf13/cobra v1.9.1
 	github.com/stretchr/testify v1.10.0
-	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
-	golang.org/x/net v0.38.0
+	golang.org/x/net v0.36.0
 	gopkg.in/yaml.v3 v3.0.1
+	k8s.io/klog/v2 v2.130.1
 	sigs.k8s.io/yaml v1.4.0
 )
 
 require (
 	aead.dev/minisign v0.3.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
-	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/term v0.30.0 // indirect
+	github.com/spf13/pflag v1.0.6 // indirect
+	golang.org/x/crypto v0.35.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/term v0.29.0 // indirect
 )
diff --git a/go.sum b/go.sum
index fa5cc70d1..6d7eea598 100644
--- a/go.sum
+++ b/go.sum
@@ -1,22 +1,27 @@
 aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
 aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
 aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
+github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=
+github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
 github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
 github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
 github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/drycc/controller-sdk-go v0.0.0-20250403055121-5e021ebe1910 h1:VIfWljt6ilB8B09GY74oe7tjokZTWwIMQ3zNwDa0DoM=
-github.com/drycc/controller-sdk-go v0.0.0-20250403055121-5e021ebe1910/go.mod h1:MbFxGlgRQUaVlThbn3DmB06EJsf+e3/O+qgbWmdvtoE=
-github.com/drycc/pkg v0.0.0-20250324060552-65752139ab2f h1:/OV4bY2yQDu0mg3l5wdJG6d2tal1hbSX+uI5eiQBQ4o=
-github.com/drycc/pkg v0.0.0-20250324060552-65752139ab2f/go.mod h1:Ss0KonBbfmUVv4bdL+8QAhLtzuBik7DS5tJTMAhycaI=
+github.com/drycc/controller-sdk-go v0.0.0-20250326034724-1dafccaadf6b h1:IA4sT1t/x/JtUm6F6T7j66ETAXwikYaUuNoQMmVp5ew=
+github.com/drycc/controller-sdk-go v0.0.0-20250326034724-1dafccaadf6b/go.mod h1:m4nhGeLJvR9bWuF12XhJ1c+hvkVY27OPN48F3l+AvAI=
+github.com/drycc/pkg v0.0.0-20241223062248-04810c5faa4b h1:wSIZheOVyeakCpI1i6xDta9e5YLZgNCqw7JfxC6R5MA=
+github.com/drycc/pkg v0.0.0-20241223062248-04810c5faa4b/go.mod h1:upGcZF/RCEqidGjSnWHh/txRC1I4GPsRGvF8/+bDfwU=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -38,21 +43,24 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
 github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -60,12 +68,12 @@ golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -74,5 +82,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
+k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/cmd/apps.go b/internal/commands/apps.go
similarity index 95%
rename from cmd/apps.go
rename to internal/commands/apps.go
index 8430a0acc..8f4d612ff 100644
--- a/cmd/apps.go
+++ b/internal/commands/apps.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -14,10 +14,11 @@ import (
 	"github.com/drycc/controller-sdk-go/appsettings"
 	"github.com/drycc/controller-sdk-go/domains"
 	"github.com/drycc/controller-sdk-go/ps"
+	"github.com/drycc/workflow-cli/internal/utils"
 	"github.com/drycc/workflow-cli/pkg/git"
 	"github.com/drycc/workflow-cli/pkg/logging"
+	"github.com/drycc/workflow-cli/pkg/settings"
 	"github.com/drycc/workflow-cli/pkg/webbrowser"
-	"github.com/drycc/workflow-cli/settings"
 	"golang.org/x/net/websocket"
 )
 
@@ -96,7 +97,7 @@ func (d *DryccCmd) AppsList(results int) error {
 
 // AppInfo prints info about app.
 func (d *DryccCmd) AppInfo(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -182,7 +183,7 @@ func (d *DryccCmd) AppInfo(appID string) error {
 
 // AppOpen opens an app in the default webbrowser.
 func (d *DryccCmd) AppOpen(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -206,7 +207,7 @@ func (d *DryccCmd) AppOpen(appID string) error {
 
 // AppLogs returns the logs from an app.
 func (d *DryccCmd) AppLogs(appID string, lines int, follow bool, timeout int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -237,7 +238,7 @@ func (d *DryccCmd) AppLogs(appID string, lines int, follow bool, timeout int) er
 
 // AppRun runs a one time command in the app.
 func (d *DryccCmd) AppRun(appID, command string, volumeVars []string, timeout, expires uint32) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -322,7 +323,7 @@ func (d *DryccCmd) AppDestroy(appID, confirm string) error {
 
 // AppTransfer transfers app ownership to another user.
 func (d *DryccCmd) AppTransfer(appID, username string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/apps_test.go b/internal/commands/apps_test.go
similarity index 99%
rename from cmd/apps_test.go
rename to internal/commands/apps_test.go
index 4f39e97a0..a71555f46 100644
--- a/cmd/apps_test.go
+++ b/internal/commands/apps_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -10,8 +10,8 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"github.com/drycc/workflow-cli/pkg/git"
+	"github.com/drycc/workflow-cli/pkg/settings"
 	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/drycc/workflow-cli/settings"
 )
 
 type expandURLCases struct {
diff --git a/cmd/auth.go b/internal/commands/auth.go
similarity index 96%
rename from cmd/auth.go
rename to internal/commands/auth.go
index bba5dfb04..82d14d676 100644
--- a/cmd/auth.go
+++ b/internal/commands/auth.go
@@ -1,9 +1,9 @@
-package cmd
+package commands
 
 import (
 	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/auth"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 // Login to a Drycc controller.
diff --git a/cmd/auth_test.go b/internal/commands/auth_test.go
similarity index 99%
rename from cmd/auth_test.go
rename to internal/commands/auth_test.go
index b499c0e17..489aee12a 100644
--- a/cmd/auth_test.go
+++ b/internal/commands/auth_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/autodeploy.go b/internal/commands/autodeploy.go
similarity index 85%
rename from cmd/autodeploy.go
rename to internal/commands/autodeploy.go
index d771478d4..21b47782e 100644
--- a/cmd/autodeploy.go
+++ b/internal/commands/autodeploy.go
@@ -1,13 +1,14 @@
-package cmd
+package commands
 
 import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // AutodeployInfo provides information about the status of app autodeploy.
 func (d *DryccCmd) AutodeployInfo(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -28,7 +29,7 @@ func (d *DryccCmd) AutodeployInfo(appID string) error {
 
 // AutodeployEnable enables an app when deploy failed
 func (d *DryccCmd) AutodeployEnable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -52,7 +53,7 @@ func (d *DryccCmd) AutodeployEnable(appID string) error {
 
 // AutodeployDisable disables an app when deploy failed
 func (d *DryccCmd) AutodeployDisable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/autodeploy_test.go b/internal/commands/autodeploy_test.go
similarity index 99%
rename from cmd/autodeploy_test.go
rename to internal/commands/autodeploy_test.go
index 1cdf4d647..63bfeb5c1 100644
--- a/cmd/autodeploy_test.go
+++ b/internal/commands/autodeploy_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/autorollback.go b/internal/commands/autorollback.go
similarity index 85%
rename from cmd/autorollback.go
rename to internal/commands/autorollback.go
index eade7596b..258fee35c 100644
--- a/cmd/autorollback.go
+++ b/internal/commands/autorollback.go
@@ -1,13 +1,14 @@
-package cmd
+package commands
 
 import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // AutorollbackInfo provides information about the status of app autorollback.
 func (d *DryccCmd) AutorollbackInfo(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -28,7 +29,7 @@ func (d *DryccCmd) AutorollbackInfo(appID string) error {
 
 // AutorollbackEnable enables an app when deploy failed
 func (d *DryccCmd) AutorollbackEnable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -52,7 +53,7 @@ func (d *DryccCmd) AutorollbackEnable(appID string) error {
 
 // AutorollbackDisable disables an app when deploy failed
 func (d *DryccCmd) AutorollbackDisable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/autorollback_test.go b/internal/commands/autorollback_test.go
similarity index 99%
rename from cmd/autorollback_test.go
rename to internal/commands/autorollback_test.go
index 7fa4df52e..4349b4dd2 100644
--- a/cmd/autorollback_test.go
+++ b/internal/commands/autorollback_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/autoscale.go b/internal/commands/autoscale.go
similarity index 88%
rename from cmd/autoscale.go
rename to internal/commands/autoscale.go
index 839948b3b..dea4799e3 100644
--- a/cmd/autoscale.go
+++ b/internal/commands/autoscale.go
@@ -1,15 +1,16 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // AutoscaleList tells the informations about app's autoscale status
 func (d *DryccCmd) AutoscaleList(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -39,7 +40,7 @@ func (d *DryccCmd) AutoscaleList(appID string) error {
 
 // AutoscaleSet sets autoscale options for the app.
 func (d *DryccCmd) AutoscaleSet(appID string, ptype string, min int, max int, CPUPercent int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -70,7 +71,7 @@ func (d *DryccCmd) AutoscaleSet(appID string, ptype string, min int, max int, CP
 
 // AutoscaleUnset removes autoscale for the app.
 func (d *DryccCmd) AutoscaleUnset(appID string, ptype string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/autoscale_test.go b/internal/commands/autoscale_test.go
similarity index 99%
rename from cmd/autoscale_test.go
rename to internal/commands/autoscale_test.go
index cedcebd34..ee120e1a9 100644
--- a/cmd/autoscale_test.go
+++ b/internal/commands/autoscale_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/builds.go b/internal/commands/builds.go
similarity index 91%
rename from cmd/builds.go
rename to internal/commands/builds.go
index 3222070a6..73d544228 100644
--- a/cmd/builds.go
+++ b/internal/commands/builds.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -6,15 +6,15 @@ import (
 	"os"
 	"path/filepath"
 
-	yaml "gopkg.in/yaml.v3"
-
 	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/builds"
+	"github.com/drycc/workflow-cli/internal/utils"
+	yaml "gopkg.in/yaml.v3"
 )
 
 // BuildsInfo lists an app's builds.
 func (d *DryccCmd) BuildsInfo(appID string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -39,7 +39,7 @@ func (d *DryccCmd) BuildsInfo(appID string, version int) error {
 
 // BuildsCreate creates a build for an app.
 func (d *DryccCmd) BuildsCreate(appID, image, stack, procfile, dryccpath, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -83,7 +83,7 @@ func (d *DryccCmd) BuildsCreate(appID, image, stack, procfile, dryccpath, confir
 }
 
 func (d *DryccCmd) BuildsFetch(appID string, version int, procfile, dryccpath, confirm string, save bool) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -132,19 +132,21 @@ func writeDryccfileToPath(d *DryccCmd, dryccpath string, dryccfile map[string]in
 	// Create the directory if it doesn't exist
 	if save {
 		os.Remove(dryccpath)
-	}
-	os.ReadDir(dryccpath)
-	err := os.MkdirAll(dryccpath, os.ModePerm)
-	if err != nil {
-		return fmt.Errorf("failed to create directory: %w", err)
+		os.ReadDir(dryccpath)
+		err := os.MkdirAll(dryccpath, os.ModePerm)
+		if err != nil {
+			return fmt.Errorf("failed to create directory: %w", err)
+		}
 	}
 
 	// Write config section
 	if config, ok := dryccfile["config"].(map[string]interface{}); ok {
 		configDir := filepath.Join(dryccpath, "config")
-		err := os.MkdirAll(configDir, os.ModePerm)
-		if err != nil {
-			return fmt.Errorf("failed to create config directory: %w", err)
+		if save {
+			err := os.MkdirAll(configDir, os.ModePerm)
+			if err != nil {
+				return fmt.Errorf("failed to create config directory: %w", err)
+			}
 		}
 
 		for key, values := range config {
@@ -179,7 +181,7 @@ func writeDryccfileToPath(d *DryccCmd, dryccpath string, dryccfile map[string]in
 			}
 			yamlContent := buf.Bytes()
 			if save {
-				err = os.WriteFile(filePath, yamlContent, 0664)
+				err := os.WriteFile(filePath, yamlContent, 0664)
 				if err != nil {
 					return fmt.Errorf("failed to write pipeline file: %w", err)
 				}
diff --git a/cmd/builds_test.go b/internal/commands/builds_test.go
similarity index 99%
rename from cmd/builds_test.go
rename to internal/commands/builds_test.go
index dc00763c9..389203ead 100644
--- a/cmd/builds_test.go
+++ b/internal/commands/builds_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/certs.go b/internal/commands/certs.go
similarity index 91%
rename from cmd/certs.go
rename to internal/commands/certs.go
index e93f4df75..4abc1a0a3 100644
--- a/cmd/certs.go
+++ b/internal/commands/certs.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -8,13 +8,14 @@ import (
 	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/certs"
 	dtime "github.com/drycc/controller-sdk-go/pkg/time"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 const dateFormat = "2 Jan 2006"
 
 // CertsList lists certs registered with the controller.
 func (d *DryccCmd) CertsList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -55,7 +56,7 @@ func (d *DryccCmd) CertsList(appID string, results int) error {
 
 // CertAdd adds a cert to the controller.
 func (d *DryccCmd) CertAdd(appID string, cert string, key string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -92,7 +93,7 @@ func (d *DryccCmd) doCertAdd(c *drycc.Client, appID string, cert string, key str
 
 // CertRemove deletes a cert from the controller.
 func (d *DryccCmd) CertRemove(appID string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if d.checkAPICompatibility(s.Client, err) != nil {
 		return err
 	}
@@ -113,7 +114,7 @@ func (d *DryccCmd) CertRemove(appID string, name string) error {
 
 // CertInfo gets info about certficiate
 func (d *DryccCmd) CertInfo(appID string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -142,7 +143,7 @@ func (d *DryccCmd) CertInfo(appID string, name string) error {
 
 // CertAttach attaches a certificate to a domain
 func (d *DryccCmd) CertAttach(appID string, name, domain string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -163,7 +164,7 @@ func (d *DryccCmd) CertAttach(appID string, name, domain string) error {
 
 // CertDetach detaches a certificate from a domain
 func (d *DryccCmd) CertDetach(appID, name, domain string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/certs_test.go b/internal/commands/certs_test.go
similarity index 99%
rename from cmd/certs_test.go
rename to internal/commands/certs_test.go
index f6ed5c030..41b552683 100644
--- a/cmd/certs_test.go
+++ b/internal/commands/certs_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/cmd.go b/internal/commands/commands.go
similarity index 97%
rename from cmd/cmd.go
rename to internal/commands/commands.go
index 571e22009..6959e8723 100644
--- a/cmd/cmd.go
+++ b/internal/commands/commands.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -9,7 +9,7 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 )
 
-// Commander is interface definition for running commands
+//go:generate mockgen -package testmocks -destination testmocks/mock_pack_client.go github.com/buildpacks/pack/internal/commands PackClient
 type Commander interface {
 	AppCreate(string, string, bool) error
 	AppsList(int) error
@@ -102,7 +102,7 @@ type Commander interface {
 	RegistryList(string, string, int) error
 	RegistrySet(string, string, string, string) error
 	RegistryUnset(string, string) error
-	ReleasesList(string, string, int) error
+	ReleasesList(string, []string, int) error
 	ReleasesInfo(string, int) error
 	ReleasesDeploy(string, []string, bool, string) error
 	ReleasesRollback(string, []string, int) error
@@ -149,7 +149,6 @@ type Commander interface {
 	ResourceUnbind(string, string) error
 }
 
-// DryccCmd is an implementation of Commander.
 type DryccCmd struct {
 	ConfigFile string
 	Warned     bool
diff --git a/cmd/config.go b/internal/commands/config.go
similarity index 95%
rename from cmd/config.go
rename to internal/commands/config.go
index 190a843f1..7020bb374 100644
--- a/cmd/config.go
+++ b/internal/commands/config.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bufio"
@@ -13,11 +13,12 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
 	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // ConfigInfo for an app
 func (d *DryccCmd) ConfigInfo(appID string, ptype string, group string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -116,7 +117,7 @@ func (d *DryccCmd) ConfigInfo(appID string, ptype string, group string, version
 
 // ConfigSet sets an app's config variables.
 func (d *DryccCmd) ConfigSet(appID string, ptype string, group string, configVars []string, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -150,7 +151,7 @@ func (d *DryccCmd) ConfigSet(appID string, ptype string, group string, configVar
 
 // ConfigUnset removes a config variable from an app.
 func (d *DryccCmd) ConfigUnset(appID string, ptype string, group string, configVars []string, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -195,7 +196,7 @@ func (d *DryccCmd) ConfigUnset(appID string, ptype string, group string, configV
 
 // ConfigPull pulls an app's config to a file.
 func (d *DryccCmd) ConfigPull(appID, ptype, group, fileName string, interactive bool, overwrite bool) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -272,7 +273,7 @@ func (d *DryccCmd) ConfigPush(appID, ptype string, group string, fileName string
 	if err != nil {
 		return err
 	}
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -318,7 +319,7 @@ func (d *DryccCmd) ConfigPush(appID, ptype string, group string, fileName string
 }
 
 func (d *DryccCmd) ConfigAttach(appID string, ptype string, groups string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -345,7 +346,7 @@ func (d *DryccCmd) ConfigAttach(appID string, ptype string, groups string) error
 }
 
 func (d *DryccCmd) ConfigDetach(appID string, ptype string, groups string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/config_test.go b/internal/commands/config_test.go
similarity index 99%
rename from cmd/config_test.go
rename to internal/commands/config_test.go
index 21ab3b8c3..13185a977 100644
--- a/cmd/config_test.go
+++ b/internal/commands/config_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/domains.go b/internal/commands/domains.go
similarity index 86%
rename from cmd/domains.go
rename to internal/commands/domains.go
index c41bc2704..17808b2d1 100644
--- a/cmd/domains.go
+++ b/internal/commands/domains.go
@@ -1,14 +1,15 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
 	"github.com/drycc/controller-sdk-go/domains"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // DomainsList lists domains registered with an app.
 func (d *DryccCmd) DomainsList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -43,7 +44,7 @@ func (d *DryccCmd) DomainsList(appID string, results int) error {
 
 // DomainsAdd adds a domain to an app.
 func (d *DryccCmd) DomainsAdd(appID, domain, ptype string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -65,7 +66,7 @@ func (d *DryccCmd) DomainsAdd(appID, domain, ptype string) error {
 
 // DomainsRemove removes a domain registered with an app.
 func (d *DryccCmd) DomainsRemove(appID, domain string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/domains_test.go b/internal/commands/domains_test.go
similarity index 99%
rename from cmd/domains_test.go
rename to internal/commands/domains_test.go
index 99fcf534a..f9cc5c590 100644
--- a/cmd/domains_test.go
+++ b/internal/commands/domains_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/gateways.go b/internal/commands/gateways.go
similarity index 89%
rename from cmd/gateways.go
rename to internal/commands/gateways.go
index acec14c50..58076908b 100644
--- a/cmd/gateways.go
+++ b/internal/commands/gateways.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/gateways"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // GatewaysList lists gateways for the app
 func (d *DryccCmd) GatewaysList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -40,7 +41,7 @@ func (d *DryccCmd) GatewaysList(appID string, results int) error {
 
 // GatewaysAdd adds a gateway to an app.
 func (d *DryccCmd) GatewaysAdd(appID, name string, port int, protocol string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -61,7 +62,7 @@ func (d *DryccCmd) GatewaysAdd(appID, name string, port int, protocol string) er
 
 // GatewaysRemove removes a gateway registered with an app.
 func (d *DryccCmd) GatewaysRemove(appID, name string, port int, protocol string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/gateways_test.go b/internal/commands/gateways_test.go
similarity index 99%
rename from cmd/gateways_test.go
rename to internal/commands/gateways_test.go
index 2ffc98d34..f18eece25 100644
--- a/cmd/gateways_test.go
+++ b/internal/commands/gateways_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/git.go b/internal/commands/git.go
similarity index 90%
rename from cmd/git.go
rename to internal/commands/git.go
index e76d2b9f2..82f2d5c11 100644
--- a/cmd/git.go
+++ b/internal/commands/git.go
@@ -1,8 +1,9 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
+	"github.com/drycc/workflow-cli/internal/utils"
 	"github.com/drycc/workflow-cli/pkg/git"
 )
 
@@ -11,7 +12,7 @@ const remoteDeletionMsg = "Git remotes for app %s removed.\n"
 
 // GitRemote creates a git remote for a drycc app.
 func (d *DryccCmd) GitRemote(appID, remote string, force bool) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -62,7 +63,7 @@ func (d *DryccCmd) GitRemote(appID, remote string, force bool) error {
 
 // GitRemove removes a application git remote from a repository
 func (d *DryccCmd) GitRemove(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/healthchecks.go b/internal/commands/healthchecks.go
similarity index 94%
rename from cmd/healthchecks.go
rename to internal/commands/healthchecks.go
index b1c1476a2..04a241b30 100644
--- a/cmd/healthchecks.go
+++ b/internal/commands/healthchecks.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,6 +6,7 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 func getHealthcheckString(ptype, probeType string, healthcheck *api.Healthcheck) string {
@@ -46,7 +47,7 @@ func getHealthchecksStrings(ptype string, healthchecks *api.Healthchecks) []stri
 
 // HealthchecksList lists an app's healthchecks.
 func (d *DryccCmd) HealthchecksList(appID, ptype string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -106,7 +107,7 @@ func (d *DryccCmd) HealthchecksList(appID, ptype string, version int) error {
 
 // HealthchecksSet sets an app's healthchecks.
 func (d *DryccCmd) HealthchecksSet(appID, healthcheckType, ptype string, probe *api.Healthcheck) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -137,7 +138,7 @@ func (d *DryccCmd) HealthchecksSet(appID, healthcheckType, ptype string, probe *
 
 // HealthchecksUnset removes an app's healthchecks.
 func (d *DryccCmd) HealthchecksUnset(appID, ptype string, healthchecks []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/healthchecks_test.go b/internal/commands/healthchecks_test.go
similarity index 99%
rename from cmd/healthchecks_test.go
rename to internal/commands/healthchecks_test.go
index 79e2e5ab4..67ba9b99d 100644
--- a/cmd/healthchecks_test.go
+++ b/internal/commands/healthchecks_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/keys.go b/internal/commands/keys.go
similarity index 98%
rename from cmd/keys.go
rename to internal/commands/keys.go
index d0d964ef6..b283887ac 100644
--- a/cmd/keys.go
+++ b/internal/commands/keys.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -10,8 +10,8 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/keys"
+	"github.com/drycc/workflow-cli/pkg/settings"
 	"github.com/drycc/workflow-cli/pkg/ssh"
-	"github.com/drycc/workflow-cli/settings"
 )
 
 // KeysList lists a user's keys.
diff --git a/cmd/keys_test.go b/internal/commands/keys_test.go
similarity index 99%
rename from cmd/keys_test.go
rename to internal/commands/keys_test.go
index 7cb419dba..c694214be 100644
--- a/cmd/keys_test.go
+++ b/internal/commands/keys_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -11,8 +11,8 @@ import (
 	"testing"
 
 	"github.com/drycc/controller-sdk-go/api"
+	"github.com/drycc/workflow-cli/pkg/settings"
 	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/drycc/workflow-cli/settings"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/cmd/labels.go b/internal/commands/labels.go
similarity index 90%
rename from cmd/labels.go
rename to internal/commands/labels.go
index 76941ad73..1d7b7f470 100644
--- a/cmd/labels.go
+++ b/internal/commands/labels.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // LabelsList list app's labels
 func (d *DryccCmd) LabelsList(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -40,7 +41,7 @@ func (d *DryccCmd) LabelsList(appID string) error {
 
 // LabelsSet sets labels for app
 func (d *DryccCmd) LabelsSet(appID string, labels []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -70,7 +71,7 @@ func (d *DryccCmd) LabelsSet(appID string, labels []string) error {
 
 // LabelsUnset removes labels for the app.
 func (d *DryccCmd) LabelsUnset(appID string, labels []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/labels_test.go b/internal/commands/labels_test.go
similarity index 99%
rename from cmd/labels_test.go
rename to internal/commands/labels_test.go
index f0633890e..cc81b5706 100644
--- a/cmd/labels_test.go
+++ b/internal/commands/labels_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/limits.go b/internal/commands/limits.go
similarity index 94%
rename from cmd/limits.go
rename to internal/commands/limits.go
index 4a51a1a7d..30329933e 100644
--- a/cmd/limits.go
+++ b/internal/commands/limits.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -7,12 +7,13 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/config"
 	"github.com/drycc/controller-sdk-go/limits"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/internal/utils"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 // LimitsList lists an app's limits.
 func (d *DryccCmd) LimitsList(appID string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -57,7 +58,7 @@ func (d *DryccCmd) LimitsList(appID string, version int) error {
 
 // LimitsSet sets an app's limits.
 func (d *DryccCmd) LimitsSet(appID string, limits []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -90,7 +91,7 @@ func (d *DryccCmd) LimitsSet(appID string, limits []string) error {
 
 // LimitsUnset removes an app's limits.
 func (d *DryccCmd) LimitsUnset(appID string, limits []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/limits_test.go b/internal/commands/limits_test.go
similarity index 99%
rename from cmd/limits_test.go
rename to internal/commands/limits_test.go
index e85e25a8a..567e450c8 100644
--- a/cmd/limits_test.go
+++ b/internal/commands/limits_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/perms.go b/internal/commands/perms.go
similarity index 85%
rename from cmd/perms.go
rename to internal/commands/perms.go
index f21e52f9d..7da480f54 100644
--- a/cmd/perms.go
+++ b/internal/commands/perms.go
@@ -1,14 +1,15 @@
-package cmd
+package commands
 
 import (
 	"strings"
 
 	"github.com/drycc/controller-sdk-go/perms"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // PermList prints which users have permissions.
 func (d *DryccCmd) PermList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -27,7 +28,7 @@ func (d *DryccCmd) PermList(appID string, results int) error {
 
 // PermCreate create user perms.
 func (d *DryccCmd) PermCreate(appID, username, permissions string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -47,7 +48,7 @@ func (d *DryccCmd) PermCreate(appID, username, permissions string) error {
 
 // PermUpdate update user perms.
 func (d *DryccCmd) PermUpdate(appID, username, permissions string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -67,7 +68,7 @@ func (d *DryccCmd) PermUpdate(appID, username, permissions string) error {
 
 // PermDelete removes a user from an app.
 func (d *DryccCmd) PermDelete(appID, username string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/perms_test.go b/internal/commands/perms_test.go
similarity index 99%
rename from cmd/perms_test.go
rename to internal/commands/perms_test.go
index a6ae8eb75..a3869af23 100644
--- a/cmd/perms_test.go
+++ b/internal/commands/perms_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/ps.go b/internal/commands/ps.go
similarity index 96%
rename from cmd/ps.go
rename to internal/commands/ps.go
index f6e1059db..e70e5e216 100644
--- a/cmd/ps.go
+++ b/internal/commands/ps.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -15,6 +15,7 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/events"
 	"github.com/drycc/controller-sdk-go/ps"
+	"github.com/drycc/workflow-cli/internal/utils"
 	"github.com/drycc/workflow-cli/pkg/logging"
 	"golang.org/x/net/websocket"
 	yaml "gopkg.in/yaml.v3"
@@ -30,7 +31,7 @@ const (
 
 // PsList lists an app's processes.
 func (d *DryccCmd) PsList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -51,7 +52,7 @@ func (d *DryccCmd) PsList(appID string, results int) error {
 
 // PodLogs returns the logs from an pod.
 func (d *DryccCmd) PsLogs(appID, podID string, lines int, follow bool, container string, previous bool) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -83,7 +84,7 @@ func (d *DryccCmd) PsLogs(appID, podID string, lines int, follow bool, container
 
 // PsList lists an app's processes.
 func (d *DryccCmd) PsExec(appID, podID string, tty, stdin bool, command []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -107,7 +108,7 @@ func (d *DryccCmd) PsExec(appID, podID string, tty, stdin bool, command []string
 
 // PsDescribe describe an app's processes.
 func (d *DryccCmd) PsDescribe(appID, podID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -199,7 +200,7 @@ func (d *DryccCmd) PsDescribe(appID, podID string) error {
 
 // PsDelete delete an pod.
 func (d *DryccCmd) PsDelete(appID string, podIDs []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/ps_test.go b/internal/commands/ps_test.go
similarity index 99%
rename from cmd/ps_test.go
rename to internal/commands/ps_test.go
index 6707c9046..45315dff0 100644
--- a/cmd/ps_test.go
+++ b/internal/commands/ps_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/pts.go b/internal/commands/pts.go
similarity index 94%
rename from cmd/pts.go
rename to internal/commands/pts.go
index 345605e9e..e2a5d7e51 100644
--- a/cmd/pts.go
+++ b/internal/commands/pts.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -10,11 +10,12 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/events"
 	"github.com/drycc/controller-sdk-go/pts"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // PtsList lists an app's processes.
 func (d *DryccCmd) PtsList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -35,7 +36,7 @@ func (d *DryccCmd) PtsList(appID string, results int) error {
 
 // PtsDescribe describe an app's processes.
 func (d *DryccCmd) PtsDescribe(appID, ptype string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -54,7 +55,7 @@ func (d *DryccCmd) PtsDescribe(appID, ptype string) error {
 
 // PtsScale scales an app's processes.
 func (d *DryccCmd) PtsScale(appID string, targets []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -82,7 +83,7 @@ func (d *DryccCmd) PtsScale(appID string, targets []string) error {
 
 // PtsRestart restarts an app's processes.
 func (d *DryccCmd) PtsRestart(appID string, targets []string, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -118,7 +119,7 @@ func (d *DryccCmd) PtsRestart(appID string, targets []string, confirm string) er
 
 // PtsRestart restarts an app's processes.
 func (d *DryccCmd) PtsClean(appID string, targets []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/pts_test.go b/internal/commands/pts_test.go
similarity index 99%
rename from cmd/pts_test.go
rename to internal/commands/pts_test.go
index c35e2b177..c2771c815 100644
--- a/cmd/pts_test.go
+++ b/internal/commands/pts_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/registry.go b/internal/commands/registry.go
similarity index 90%
rename from cmd/registry.go
rename to internal/commands/registry.go
index e3fe8b209..3c8b562a0 100644
--- a/cmd/registry.go
+++ b/internal/commands/registry.go
@@ -1,15 +1,16 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // RegistryList lists an app's registry information.
 func (d *DryccCmd) RegistryList(appID, ptype string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -51,7 +52,7 @@ func (d *DryccCmd) RegistryList(appID, ptype string, version int) error {
 
 // RegistrySet sets an app's registry information.
 func (d *DryccCmd) RegistrySet(appID, ptype, username, password string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -83,7 +84,7 @@ func (d *DryccCmd) RegistrySet(appID, ptype, username, password string) error {
 
 // RegistryUnset removes an app's registry information.
 func (d *DryccCmd) RegistryUnset(appID, ptype string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/registry_test.go b/internal/commands/registry_test.go
similarity index 99%
rename from cmd/registry_test.go
rename to internal/commands/registry_test.go
index 2977528c2..f1f1ac3e4 100644
--- a/cmd/registry_test.go
+++ b/internal/commands/registry_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/releases.go b/internal/commands/releases.go
similarity index 90%
rename from cmd/releases.go
rename to internal/commands/releases.go
index 524893f82..8f1d9d1bf 100644
--- a/cmd/releases.go
+++ b/internal/commands/releases.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 	"time"
 
 	"github.com/drycc/controller-sdk-go/releases"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // ReleasesList lists an app's releases.
-func (d *DryccCmd) ReleasesList(appID, ptypes string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+func (d *DryccCmd) ReleasesList(appID string, targets []string, results int) error {
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -19,7 +20,7 @@ func (d *DryccCmd) ReleasesList(appID, ptypes string, results int) error {
 	if results == defaultLimit {
 		results = s.Limit
 	}
-
+	ptypes := strings.Join(targets, ",")
 	releases, count, err := releases.List(s.Client, appID, ptypes, results)
 	if d.checkAPICompatibility(s.Client, err) != nil {
 		return err
@@ -42,7 +43,7 @@ func (d *DryccCmd) ReleasesList(appID, ptypes string, results int) error {
 
 // ReleasesInfo prints info about a specific release.
 func (d *DryccCmd) ReleasesInfo(appID string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -94,7 +95,7 @@ func (d *DryccCmd) ReleasesInfo(appID string, version int) error {
 
 // ReleasesDeploy force deploy lastest release.
 func (d *DryccCmd) ReleasesDeploy(appID string, targets []string, force bool, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -131,7 +132,7 @@ func (d *DryccCmd) ReleasesDeploy(appID string, targets []string, force bool, co
 
 // ReleasesRollback rolls an app back to a previous release.
 func (d *DryccCmd) ReleasesRollback(appID string, targets []string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/releases_test.go b/internal/commands/releases_test.go
similarity index 98%
rename from cmd/releases_test.go
rename to internal/commands/releases_test.go
index ba705829d..695a1a7a1 100644
--- a/cmd/releases_test.go
+++ b/internal/commands/releases_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -57,7 +57,7 @@ func TestReleasesList(t *testing.T) {
 		}`)
 	})
 
-	err = cmdr.ReleasesList("numenor", "", -1)
+	err = cmdr.ReleasesList("numenor", []string{}, -1)
 	assert.NoError(t, err)
 	assert.Equal(t, b.String(), `OWNER     STATE      VERSION    CREATED                   SUMMARY                        
 nazgul    succeed    v2         2016-08-22T17:40:16Z      khamul added ANGMAR               
@@ -98,7 +98,7 @@ func TestReleasesListLimit(t *testing.T) {
 		}`)
 	})
 
-	err = cmdr.ReleasesList("numenor", "", 1)
+	err = cmdr.ReleasesList("numenor", []string{}, 1)
 	assert.NoError(t, err)
 	assert.Equal(t, b.String(), `OWNER     STATE      VERSION    CREATED                 SUMMARY             
 nazgul    succeed    v2         2016-08-22T17:40:16Z    khamul added ANGMAR    
diff --git a/cmd/resources.go b/internal/commands/resources.go
similarity index 93%
rename from cmd/resources.go
rename to internal/commands/resources.go
index 00f89233d..dec457c10 100644
--- a/cmd/resources.go
+++ b/internal/commands/resources.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"encoding/base64"
@@ -11,7 +11,8 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/resources"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/internal/utils"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 // ResourceServices list resource service
@@ -80,7 +81,7 @@ func (d *DryccCmd) ResourcesPlans(serviceName string, results int) error {
 
 // ResourcesCreate create a resource for the application
 func (d *DryccCmd) ResourcesCreate(appID, plan string, name string, params []string, values string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -132,7 +133,7 @@ func (d *DryccCmd) ResourcesCreate(appID, plan string, name string, params []str
 
 // ResourcesList list resources in the application
 func (d *DryccCmd) ResourcesList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -165,7 +166,7 @@ func (d *DryccCmd) ResourcesList(appID string, results int) error {
 
 // ResourceGet describe a resource from the application
 func (d *DryccCmd) ResourceGet(appID, name string, details bool) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -204,7 +205,7 @@ func (d *DryccCmd) ResourceGet(appID, name string, details bool) error {
 
 // ResourceDelete delete a resource from the application
 func (d *DryccCmd) ResourceDelete(appID, name, confirm string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -240,7 +241,7 @@ func (d *DryccCmd) ResourceDelete(appID, name, confirm string) error {
 
 // ResourcePut update a resource for the application
 func (d *DryccCmd) ResourcePut(appID, plan string, name string, params []string, values string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -291,7 +292,7 @@ func (d *DryccCmd) ResourcePut(appID, plan string, name string, params []string,
 
 // ResourceBind mount a resource to process of the application
 func (d *DryccCmd) ResourceBind(appID string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -315,7 +316,7 @@ func (d *DryccCmd) ResourceBind(appID string, name string) error {
 
 // ResourceUnbind resource a resource the application
 func (d *DryccCmd) ResourceUnbind(appID string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/resources_test.go b/internal/commands/resources_test.go
similarity index 99%
rename from cmd/resources_test.go
rename to internal/commands/resources_test.go
index 275377202..16b5be4ab 100644
--- a/cmd/resources_test.go
+++ b/internal/commands/resources_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/routes.go b/internal/commands/routes.go
similarity index 89%
rename from cmd/routes.go
rename to internal/commands/routes.go
index e15a2a32d..b51754a1b 100644
--- a/cmd/routes.go
+++ b/internal/commands/routes.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"encoding/json"
@@ -7,12 +7,13 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/routes"
+	"github.com/drycc/workflow-cli/internal/utils"
 	"sigs.k8s.io/yaml"
 )
 
 // RoutesCreate create a route to an app.
 func (d *DryccCmd) RoutesCreate(appID, name string, kind string, backendRefs ...api.BackendRefRequest) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -33,7 +34,7 @@ func (d *DryccCmd) RoutesCreate(appID, name string, kind string, backendRefs ...
 
 // RoutesList lists routes for the app
 func (d *DryccCmd) RoutesList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -82,7 +83,7 @@ func (d *DryccCmd) RoutesList(appID string, results int) error {
 
 // RoutesAttach bind a route to gateway.
 func (d *DryccCmd) RoutesAttach(appID, name string, port int, gateway string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -103,7 +104,7 @@ func (d *DryccCmd) RoutesAttach(appID, name string, port int, gateway string) er
 
 // RoutesDetach bind a route to gateway.
 func (d *DryccCmd) RoutesDetach(appID, name string, port int, gateway string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -124,7 +125,7 @@ func (d *DryccCmd) RoutesDetach(appID, name string, port int, gateway string) er
 
 // RoutesGet get rule of route for the app
 func (d *DryccCmd) RoutesGet(appID string, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -146,7 +147,7 @@ func (d *DryccCmd) RoutesGet(appID string, name string) error {
 
 // RoutesSet set rule of route for the app
 func (d *DryccCmd) RoutesSet(appID string, name string, ruleFile string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -177,7 +178,7 @@ func (d *DryccCmd) RoutesSet(appID string, name string, ruleFile string) error {
 
 // RoutesRemove removes a route registered with an app.
 func (d *DryccCmd) RoutesRemove(appID, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/routes_test.go b/internal/commands/routes_test.go
similarity index 99%
rename from cmd/routes_test.go
rename to internal/commands/routes_test.go
index 6e8975beb..d692bd3e0 100644
--- a/cmd/routes_test.go
+++ b/internal/commands/routes_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/routing.go b/internal/commands/routing.go
similarity index 85%
rename from cmd/routing.go
rename to internal/commands/routing.go
index ff8113bf5..9dfa5d5bc 100644
--- a/cmd/routing.go
+++ b/internal/commands/routing.go
@@ -1,13 +1,14 @@
-package cmd
+package commands
 
 import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // RoutingInfo provides information about the status of app routing.
 func (d *DryccCmd) RoutingInfo(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -28,7 +29,7 @@ func (d *DryccCmd) RoutingInfo(appID string) error {
 
 // RoutingEnable enables an app from being exposed by the router.
 func (d *DryccCmd) RoutingEnable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -53,7 +54,7 @@ func (d *DryccCmd) RoutingEnable(appID string) error {
 
 // RoutingDisable disables an app from being exposed by the router.
 func (d *DryccCmd) RoutingDisable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/routing_test.go b/internal/commands/routing_test.go
similarity index 99%
rename from cmd/routing_test.go
rename to internal/commands/routing_test.go
index 0830a125c..073c286cb 100644
--- a/cmd/routing_test.go
+++ b/internal/commands/routing_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/services.go b/internal/commands/services.go
similarity index 90%
rename from cmd/services.go
rename to internal/commands/services.go
index 6154773bd..2fca6ded1 100644
--- a/cmd/services.go
+++ b/internal/commands/services.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 	"strconv"
 
 	"github.com/drycc/controller-sdk-go/services"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // ServicesList lists extra services for the app
 func (d *DryccCmd) ServicesList(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -43,7 +44,7 @@ func (d *DryccCmd) ServicesList(appID string) error {
 
 // ServicesAdd adds a service to an app.
 func (d *DryccCmd) ServicesAdd(appID, ptype string, ports string, protocol string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -68,7 +69,7 @@ func (d *DryccCmd) ServicesAdd(appID, ptype string, ports string, protocol strin
 
 // ServicesRemove removes a service for Ptype registered with an app.
 func (d *DryccCmd) ServicesRemove(appID, ptype string, protocol string, port int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/tags.go b/internal/commands/tags.go
similarity index 81%
rename from cmd/tags.go
rename to internal/commands/tags.go
index 8f2632401..a4119bc29 100644
--- a/cmd/tags.go
+++ b/internal/commands/tags.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // TagsList lists an app's tags.
 func (d *DryccCmd) TagsList(appID, ptype string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -22,24 +23,39 @@ func (d *DryccCmd) TagsList(appID, ptype string, version int) error {
 	}
 	if len(config.Tags) == 0 {
 		d.Println(fmt.Sprintf("No tags found in %s app.", appID))
+		return nil
+	}
+
+	ptypes := []string{}
+	if ptype != "" {
+		ptypes = append(ptypes, ptype)
+
 	} else {
+		for ptype := range config.Tags {
+			ptypes = append(ptypes, ptype)
+		}
+	}
+
+	table := d.getDefaultFormatTable([]string{"PTYPE", "KEY", "VALUE"})
+	for _, ptype := range sortPtypes(ptypes) {
 		if tags, ok := config.Tags[ptype]; ok {
-			table := d.getDefaultFormatTable([]string{"KEY", "VALUE"})
 			for _, key := range *sortKeys(tags) {
 				table.Append([]string{
+					ptype,
 					key,
 					fmt.Sprintf("%v", config.Tags[ptype][key]),
 				})
 			}
-			table.Render()
 		}
 	}
+	table.Render()
+
 	return nil
 }
 
 // TagsSet sets an app's tags.
 func (d *DryccCmd) TagsSet(appID, ptype string, tags []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -70,7 +86,7 @@ func (d *DryccCmd) TagsSet(appID, ptype string, tags []string) error {
 
 // TagsUnset removes an app's tags.
 func (d *DryccCmd) TagsUnset(appID, ptype string, tags []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/tags_test.go b/internal/commands/tags_test.go
similarity index 95%
rename from cmd/tags_test.go
rename to internal/commands/tags_test.go
index d2f20556e..3256fad14 100644
--- a/cmd/tags_test.go
+++ b/internal/commands/tags_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -108,9 +108,9 @@ func TestTagsList(t *testing.T) {
 
 	err = cmdr.TagsList("enterprise", "web", -1)
 	assert.NoError(t, err)
-	assert.Equal(t, b.String(), `KEY     VALUE 
-ncc     1701     
-warp    8        
+	assert.Equal(t, b.String(), `PTYPE    KEY     VALUE 
+web      ncc     1701     
+web      warp    8        
 `, "output")
 }
 
@@ -158,8 +158,8 @@ func TestTagsSet(t *testing.T) {
 
 	assert.Equal(t, testutil.StripProgress(b.String()), `Applying tags... done
 
-KEY     VALUE 
-true    false    
+PTYPE    KEY     VALUE 
+web      true    false    
 `, "output")
 }
 
@@ -207,7 +207,7 @@ func TestTagsUnset(t *testing.T) {
 
 	assert.Equal(t, testutil.StripProgress(b.String()), `Applying tags... done
 
-KEY     VALUE 
-warp    8        
+PTYPE    KEY     VALUE 
+web      warp    8        
 `, "output")
 }
diff --git a/cmd/timeouts.go b/internal/commands/timeouts.go
similarity index 91%
rename from cmd/timeouts.go
rename to internal/commands/timeouts.go
index 417a9f2cf..93570ffd6 100644
--- a/cmd/timeouts.go
+++ b/internal/commands/timeouts.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -6,11 +6,12 @@ import (
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // TimeoutsList lists an app's timeouts.
 func (d *DryccCmd) TimeoutsList(appID string, version int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -39,7 +40,7 @@ func (d *DryccCmd) TimeoutsList(appID string, version int) error {
 
 // TimeoutsSet sets an app's timeouts.
 func (d *DryccCmd) TimeoutsSet(appID string, timeouts []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -71,7 +72,7 @@ func (d *DryccCmd) TimeoutsSet(appID string, timeouts []string) error {
 
 // TimeoutsUnset removes an app's timeouts.
 func (d *DryccCmd) TimeoutsUnset(appID string, timeouts []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/timeouts_test.go b/internal/commands/timeouts_test.go
similarity index 99%
rename from cmd/timeouts_test.go
rename to internal/commands/timeouts_test.go
index bfacef3b4..3d27febc3 100644
--- a/cmd/timeouts_test.go
+++ b/internal/commands/timeouts_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/tls.go b/internal/commands/tls.go
similarity index 89%
rename from cmd/tls.go
rename to internal/commands/tls.go
index 125be9cd2..6b32213d8 100644
--- a/cmd/tls.go
+++ b/internal/commands/tls.go
@@ -1,14 +1,15 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
 	"github.com/drycc/controller-sdk-go/tls"
+	"github.com/drycc/workflow-cli/internal/utils"
 )
 
 // TLSInfo prints info about the TLS settings for the given app.
 func (d *DryccCmd) TLSInfo(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -46,7 +47,7 @@ func (d *DryccCmd) TLSInfo(appID string) error {
 
 // TLSForceEnable enables the router to enforce https-only requests to the application.
 func (d *DryccCmd) TLSForceEnable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -68,7 +69,7 @@ func (d *DryccCmd) TLSForceEnable(appID string) error {
 
 // TLSForceDisable disables the router to enforce https-only requests to the application.
 func (d *DryccCmd) TLSForceDisable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -90,7 +91,7 @@ func (d *DryccCmd) TLSForceDisable(appID string) error {
 
 // TLSAutoEnable enables certs-auto requests to the application.
 func (d *DryccCmd) TLSAutoEnable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -112,7 +113,7 @@ func (d *DryccCmd) TLSAutoEnable(appID string) error {
 
 // TLSAutoDisable disables certs-auto requests to the application.
 func (d *DryccCmd) TLSAutoDisable(appID string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -134,7 +135,7 @@ func (d *DryccCmd) TLSAutoDisable(appID string) error {
 
 // TLSAutoIssuer add issuer requests to the application.
 func (d *DryccCmd) TLSAutoIssuer(appID string, email string, server string, keyID string, keySecret string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
diff --git a/cmd/tls_test.go b/internal/commands/tls_test.go
similarity index 99%
rename from cmd/tls_test.go
rename to internal/commands/tls_test.go
index eecc393cd..071e448b1 100644
--- a/cmd/tls_test.go
+++ b/internal/commands/tls_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/token.go b/internal/commands/token.go
similarity index 98%
rename from cmd/token.go
rename to internal/commands/token.go
index 40ab88e74..8c8c81cd5 100644
--- a/cmd/token.go
+++ b/internal/commands/token.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"errors"
@@ -12,7 +12,7 @@ import (
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/auth"
 	"github.com/drycc/controller-sdk-go/tokens"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 func (d *DryccCmd) TokensList(results int) error {
diff --git a/cmd/token_test.go b/internal/commands/token_test.go
similarity index 99%
rename from cmd/token_test.go
rename to internal/commands/token_test.go
index 68a5a092d..1e6d66451 100644
--- a/cmd/token_test.go
+++ b/internal/commands/token_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/update.go b/internal/commands/update.go
similarity index 92%
rename from cmd/update.go
rename to internal/commands/update.go
index 6d87eb83c..adc52b1ac 100644
--- a/cmd/update.go
+++ b/internal/commands/update.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -11,11 +11,11 @@ import (
 	"github.com/minio/selfupdate"
 )
 
-const WorkflowCliURL = "https://www.drycc.cc/workflow-cli.txt"
+const workflowCliURL = "https://www.drycc.cc/workflow-cli.txt"
 
 func (d *DryccCmd) latestVersion() (string, string, error) {
 	quit := progress(d.WOut)
-	resp, err := http.Get(WorkflowCliURL)
+	resp, err := http.Get(workflowCliURL)
 	quit <- true
 	<-quit
 	if err != nil {
diff --git a/cmd/users.go b/internal/commands/users.go
similarity index 96%
rename from cmd/users.go
rename to internal/commands/users.go
index 1dcecbc0e..a60634651 100644
--- a/cmd/users.go
+++ b/internal/commands/users.go
@@ -1,10 +1,10 @@
-package cmd
+package commands
 
 import (
 	"fmt"
 
 	"github.com/drycc/controller-sdk-go/users"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 // UsersList lists users registered with the controller.
diff --git a/cmd/users_test.go b/internal/commands/users_test.go
similarity index 99%
rename from cmd/users_test.go
rename to internal/commands/users_test.go
index ab0595935..bb0eb36e2 100644
--- a/cmd/users_test.go
+++ b/internal/commands/users_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/utils.go b/internal/commands/utils.go
similarity index 90%
rename from cmd/utils.go
rename to internal/commands/utils.go
index bdf02ec91..0a8f88eba 100644
--- a/cmd/utils.go
+++ b/internal/commands/utils.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
@@ -12,8 +12,6 @@ import (
 
 	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/api"
-	"github.com/drycc/workflow-cli/pkg/git"
-	"github.com/drycc/workflow-cli/settings"
 	"github.com/olekukonko/tablewriter"
 	yaml "gopkg.in/yaml.v3"
 )
@@ -44,25 +42,6 @@ func progress(wOut io.Writer) chan bool {
 	return quit
 }
 
-// load loads settings file and looks up the app name
-func load(cf string, appID string) (*settings.Settings, string, error) {
-	s, err := settings.Load(cf)
-
-	if err != nil {
-		return nil, "", err
-	}
-
-	if appID == "" {
-		appID, err = git.DetectAppName(git.DefaultCmd, s.Client.ControllerURL.Host)
-
-		if err != nil {
-			return nil, "", err
-		}
-	}
-
-	return s, appID, nil
-}
-
 func drinkOfChoice() string {
 	drink := os.Getenv("DRYCC_DRINK_OF_CHOICE")
 
@@ -206,3 +185,10 @@ func safeGetString(data string) string {
 	}
 	return data
 }
+
+func ResponseLimit(limit int) (int, error) {
+	if limit == 0 {
+		return -1, nil
+	}
+	return limit, nil
+}
diff --git a/cmd/utils_test.go b/internal/commands/utils_test.go
similarity index 56%
rename from cmd/utils_test.go
rename to internal/commands/utils_test.go
index 0a99c97c2..a44c8aa53 100644
--- a/cmd/utils_test.go
+++ b/internal/commands/utils_test.go
@@ -1,53 +1,14 @@
-package cmd
+package commands
 
 import (
 	"bytes"
 	"os"
-	"path/filepath"
 	"testing"
 
 	drycc "github.com/drycc/controller-sdk-go"
-	"github.com/drycc/workflow-cli/pkg/git"
-	"github.com/drycc/workflow-cli/settings"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestLoad(t *testing.T) {
-	name, err := os.MkdirTemp("", "drycc-cli-unit-test-load")
-	assert.NoError(t, err)
-	defer os.RemoveAll(name)
-
-	filename := filepath.Join(name, "test.json")
-	host := "drycc.example.com"
-	client, err := drycc.New(false, host, "")
-	assert.NoError(t, err)
-
-	config := settings.Settings{
-		Username: "test",
-		Client:   client,
-	}
-
-	filename, err = config.Save(filename)
-	assert.NoError(t, err)
-
-	_, appID, err := load(filename, "test")
-	assert.NoError(t, err)
-	assert.Equal(t, appID, "test", "app")
-
-	assert.NoError(t, os.Chdir(name))
-
-	_, appID, err = load(filename, "")
-	assert.NoError(t, err)
-	assert.Equal(t, appID, filepath.Base(name), "app")
-
-	assert.NoError(t, git.Init(git.DefaultCmd))
-	assert.NoError(t, git.CreateRemote(git.DefaultCmd, host, "drycc", "testing"))
-
-	_, appID, err = load(filename, "")
-	assert.NoError(t, err)
-	assert.Equal(t, appID, "testing", "app")
-}
-
 func TestDrinkOfChoice(t *testing.T) {
 	os.Setenv("DRYCC_DRINK_OF_CHOICE", "test")
 	assert.Equal(t, drinkOfChoice(), "test", "output")
diff --git a/cmd/version.go b/internal/commands/version.go
similarity index 91%
rename from cmd/version.go
rename to internal/commands/version.go
index 0727fe7c1..fd27162b5 100644
--- a/cmd/version.go
+++ b/internal/commands/version.go
@@ -1,8 +1,8 @@
-package cmd
+package commands
 
 import (
 	drycc "github.com/drycc/controller-sdk-go"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/pkg/settings"
 	"github.com/drycc/workflow-cli/version"
 )
 
diff --git a/cmd/version_test.go b/internal/commands/version_test.go
similarity index 98%
rename from cmd/version_test.go
rename to internal/commands/version_test.go
index 35bf13648..7522350b4 100644
--- a/cmd/version_test.go
+++ b/internal/commands/version_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/cmd/volumes.go b/internal/commands/volumes.go
similarity index 94%
rename from cmd/volumes.go
rename to internal/commands/volumes.go
index a9ecb72d7..3197c3e7c 100644
--- a/cmd/volumes.go
+++ b/internal/commands/volumes.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"fmt"
@@ -14,13 +14,14 @@ import (
 	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/volumes"
+	"github.com/drycc/workflow-cli/internal/utils"
 	"github.com/schollz/progressbar/v3"
 	"sigs.k8s.io/yaml"
 )
 
 // VolumesList list volumes in the application
 func (d *DryccCmd) VolumesList(appID string, results int) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -44,7 +45,7 @@ func (d *DryccCmd) VolumesList(appID string, results int) error {
 
 // VolumesInfo get volume in the application
 func (d *DryccCmd) VolumesInfo(appID, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -81,7 +82,7 @@ func (d *DryccCmd) VolumesInfo(appID, name string) error {
 
 // VolumesCreate create a volume for the application
 func (d *DryccCmd) VolumesCreate(appID, name, vType, size string, parameters map[string]interface{}) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -114,7 +115,7 @@ Examples: 2G 2g`, size)
 
 // VolumesExpand create a volume for the application
 func (d *DryccCmd) VolumesExpand(appID, name, size string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -145,7 +146,7 @@ Examples: 2G 2g`, size)
 
 // VolumesDelete delete a volume from the application
 func (d *DryccCmd) VolumesDelete(appID, name string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -181,7 +182,7 @@ func (d *DryccCmd) VolumesClient(appID, cmd string, args ...string) error {
 
 // VolumesMount mount a volume to process of the application
 func (d *DryccCmd) VolumesMount(appID string, name string, volumeVars []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -211,7 +212,7 @@ func (d *DryccCmd) VolumesMount(appID string, name string, volumeVars []string)
 
 // VolumesUnmount unmount a volume from process of the application
 func (d *DryccCmd) VolumesUnmount(appID string, name string, volumeVars []string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 
 	if err != nil {
 		return err
@@ -242,7 +243,7 @@ func (d *DryccCmd) VolumesUnmount(appID string, name string, volumeVars []string
 // volumesClientLs get all directory entries sorted by filename.
 func (d *DryccCmd) volumesClientLs(appID, vol string) error {
 
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -346,7 +347,7 @@ func (d *DryccCmd) volumesClientPostAll(client *drycc.Client, appID, volumeID, v
 
 // volumesClientCp copy files between volume and local file
 func (d *DryccCmd) volumesClientCp(appID, src, dst string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
@@ -387,7 +388,7 @@ func (d *DryccCmd) volumesClientCp(appID, src, dst string) error {
 
 // volumesClientRm delete a file from volume
 func (d *DryccCmd) volumesClientRm(appID, vol string) error {
-	s, appID, err := load(d.ConfigFile, appID)
+	appID, s, err := utils.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/volumes_test.go b/internal/commands/volumes_test.go
similarity index 99%
rename from cmd/volumes_test.go
rename to internal/commands/volumes_test.go
index f46c08f02..4934a7ae3 100644
--- a/cmd/volumes_test.go
+++ b/internal/commands/volumes_test.go
@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"bytes"
diff --git a/internal/completion/completion.go b/internal/completion/completion.go
new file mode 100644
index 000000000..3f3f99349
--- /dev/null
+++ b/internal/completion/completion.go
@@ -0,0 +1,876 @@
+package completion
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"slices"
+
+	"github.com/drycc/controller-sdk-go/apps"
+	"github.com/drycc/controller-sdk-go/appsettings"
+	"github.com/drycc/controller-sdk-go/certs"
+	"github.com/drycc/controller-sdk-go/config"
+	"github.com/drycc/controller-sdk-go/domains"
+	"github.com/drycc/controller-sdk-go/gateways"
+	"github.com/drycc/controller-sdk-go/keys"
+	"github.com/drycc/controller-sdk-go/limits"
+	"github.com/drycc/controller-sdk-go/perms"
+	"github.com/drycc/controller-sdk-go/ps"
+	"github.com/drycc/controller-sdk-go/pts"
+	"github.com/drycc/controller-sdk-go/releases"
+	"github.com/drycc/controller-sdk-go/resources"
+	"github.com/drycc/controller-sdk-go/routes"
+	"github.com/drycc/controller-sdk-go/services"
+	"github.com/drycc/controller-sdk-go/tokens"
+	"github.com/drycc/controller-sdk-go/volumes"
+	"github.com/drycc/workflow-cli/internal/utils"
+	"github.com/drycc/workflow-cli/pkg/settings"
+	"github.com/spf13/cobra"
+)
+
+type Completion interface {
+	CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
+}
+
+type AppCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *AppCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if apps, _, err := apps.List(s.Client, -1); err == nil {
+			var results []string
+			for _, app := range apps {
+				if strings.HasPrefix(app.ID, toComplete) {
+					results = append(results, app.ID)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type CertCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *CertCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if certs, _, err := certs.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, cert := range certs {
+				if strings.HasPrefix(cert.Name, toComplete) {
+					results = append(results, cert.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type CertDomainTachCompletion struct {
+	AppID      *string
+	ConfigFile *string
+}
+
+func (c *CertDomainTachCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		certCompletion := CertCompletion{AppID: c.AppID, ConfigFile: c.ConfigFile}
+		return certCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		domainCompletion := DomainCompletion{AppID: c.AppID, ArgsLen: 1, ConfigFile: c.ConfigFile}
+		return domainCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type ConfigPtsGroupArgsCompletion struct {
+	AppID      *string
+	ConfigFile *string
+}
+
+func (c *ConfigPtsGroupArgsCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		ptsCompletion := PtsCompletion{AppID: c.AppID, ConfigFile: c.ConfigFile}
+		return ptsCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		groups := args[1:]
+		if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil {
+			if config, err := config.List(s.Client, appID, -1); err == nil {
+				var results []string
+				for _, value := range config.Values {
+					if value.Group != "" && strings.HasPrefix(value.Group, toComplete) {
+						if !slices.Contains(groups, value.Group) && !slices.Contains(results, value.Group) {
+							results = append(results, value.Group)
+						}
+					}
+				}
+				return results, cobra.ShellCompDirectiveNoFileComp
+			}
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ConfigGroupCompletion struct {
+	AppID      *string
+	ConfigFile *string
+	ArgsLen    int
+}
+
+func (c *ConfigGroupCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if config, err := config.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, value := range config.Values {
+				if value.Group != "" && strings.HasPrefix(value.Group, toComplete) {
+					results = append(results, value.Group)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type DomainCompletion struct {
+	AppID      *string
+	ConfigFile *string
+	ArgsLen    int
+}
+
+func (c *DomainCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if domains, _, err := domains.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, domain := range domains {
+				if strings.HasPrefix(domain.Domain, toComplete) {
+					results = append(results, domain.Domain)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type GatewayProtocolCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *GatewayProtocolCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	protocols := []string{"TCP", "UDP", "TLS", "HTTP", "HTTPS"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, protocol := range protocols {
+			if strings.HasPrefix(protocol, toComplete) {
+				results = append(results, protocol)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type GatewayNameCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *GatewayNameCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	var results []string
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if gateways, _, err := gateways.List(s.Client, appID, -1); err == nil {
+			for _, gateway := range gateways {
+				if strings.HasPrefix(gateway.Name, toComplete) {
+					results = append(results, gateway.Name)
+				}
+			}
+		}
+	}
+	return results, cobra.ShellCompDirectiveNoFileComp
+}
+
+type HealthTypeCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *HealthTypeCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	healthTypes := []string{"startupProbe", "livenessProbe", "readinessProbe"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, healthType := range healthTypes {
+			if strings.HasPrefix(healthType, toComplete) {
+				results = append(results, healthType)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ProbeTypeCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ProbeTypeCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	probeTypes := []string{"httpGet", "exec", "tcpSocket"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, probeType := range probeTypes {
+			if strings.HasPrefix(probeType, toComplete) {
+				results = append(results, probeType)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type HealthChecksCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *HealthChecksCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		healthTypeCompletion := HealthTypeCompletion{ArgsLen: 0, ConfigFile: c.ConfigFile}
+		return healthTypeCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		probeTypeCompletion := ProbeTypeCompletion{ArgsLen: 1, ConfigFile: c.ConfigFile}
+		return probeTypeCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type ServiceProtocolCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ServiceProtocolCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	protocols := []string{"TCP", "UDP", "TLS", "SCTP"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, protocol := range protocols {
+			if strings.HasPrefix(protocol, toComplete) {
+				results = append(results, protocol)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type KeyCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *KeyCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if keys, _, err := keys.List(s.Client, -1); err == nil {
+			var results []string
+			for _, key := range keys {
+				if strings.HasPrefix(key.ID, toComplete) {
+					results = append(results, key.ID)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type LabelCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *LabelCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if appsettings, err := appsettings.List(s.Client, appID); err == nil {
+			var results []string
+			for key := range appsettings.Label {
+				if strings.HasPrefix(key, toComplete) {
+					if !slices.Contains(args, key) {
+						results = append(results, key)
+					}
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type LimitSpecCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *LimitSpecCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if specs, _, err := limits.Specs(s.Client, toComplete, -1); err == nil {
+			var results []string
+			for _, sepc := range specs {
+				results = append(results, sepc.ID)
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type LimitSetPlanCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *LimitSetPlanCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if strings.Contains(toComplete, "=") {
+		var results []string
+		parts := strings.Split(toComplete, "=")
+		if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+			if plans, _, err := limits.Plans(s.Client, "", 0, 0, -1); err == nil {
+				for _, plan := range plans {
+					if strings.HasPrefix(plan.ID, parts[1]) {
+						results = append(results, fmt.Sprintf("%s=%s", parts[0], plan.ID))
+					}
+				}
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+	} else {
+		ptsSetArgsCompletion := PtsSetArgsCompletion{
+			PtsCompletion: &PtsCompletion{AppID: c.AppID, ArgsLen: -1, ConfigFile: c.ConfigFile},
+		}
+		return ptsSetArgsCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type UserPermsCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *UserPermsCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	perms := []string{"view", "change", "delete"}
+	if len(args) > 0 && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+
+		var results []string
+		for _, perm := range perms {
+			if strings.HasPrefix(perm, toComplete) {
+				results = append(results, perm)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type UserPermsArgsCompletion struct {
+	*UserPermsCompletion
+}
+
+func (c *UserPermsArgsCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	c.ArgsLen = -1
+	var results []string
+	completes, _ := c.UserPermsCompletion.CompletionFunc(cmd, args, toComplete)
+	for _, complete := range completes {
+		if !slices.Contains(args, complete) {
+			results = append(results, complete)
+		}
+	}
+	return results, cobra.ShellCompDirectiveNoFileComp
+}
+
+type PermUsernameCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *PermUsernameCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+			if perms, _, err := perms.List(s.Client, appID, -1); err == nil {
+				var results []string
+				for _, perm := range perms {
+					if strings.HasPrefix(perm.Username, toComplete) {
+						results = append(results, perm.Username)
+					}
+				}
+				return results, cobra.ShellCompDirectiveNoFileComp
+			}
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type PermUpdateCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *PermUpdateCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		permUsernameCompletion := PermUsernameCompletion{AppID: c.AppID, ArgsLen: 0, ConfigFile: c.ConfigFile}
+		return permUsernameCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		userPermsArgsCompletion := UserPermsArgsCompletion{
+			UserPermsCompletion: &UserPermsCompletion{ConfigFile: c.ConfigFile},
+		}
+		return userPermsArgsCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type PsCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *PsCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if pods, _, err := ps.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, pod := range pods {
+				if strings.HasPrefix(pod.Name, toComplete) {
+					results = append(results, pod.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type PtsCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *PtsCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	var results []string
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if ptypes, _, err := pts.List(s.Client, appID, -1); err == nil {
+			for _, ptype := range ptypes {
+				if strings.HasPrefix(ptype.Name, toComplete) {
+					if !slices.Contains(args, ptype.Name) {
+						results = append(results, ptype.Name)
+					}
+				}
+			}
+		}
+	}
+	return results, cobra.ShellCompDirectiveNoFileComp
+}
+
+type PtsArgsCompletion struct {
+	*PtsCompletion
+}
+
+func (c *PtsArgsCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	var results []string
+	completes, _ := c.PtsCompletion.CompletionFunc(cmd, args, toComplete)
+	for _, complete := range completes {
+		if !slices.Contains(args, complete) {
+			results = append(results, complete)
+		}
+	}
+	return results, cobra.ShellCompDirectiveNoFileComp
+}
+
+type PtsSetArgsCompletion struct {
+	*PtsCompletion
+}
+
+func (c *PtsSetArgsCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	var results []string
+	completion := PtsCompletion{AppID: c.AppID, ArgsLen: -1, ConfigFile: c.ConfigFile}
+	completes, _ := completion.CompletionFunc(cmd, args, toComplete)
+	for _, complete := range completes {
+		hasArgs := false
+		result := fmt.Sprintf("%s=", complete)
+		for _, arg := range args {
+			if strings.HasPrefix(arg, result) {
+				hasArgs = true
+				break
+			}
+		}
+		if !hasArgs {
+			results = append(results, result)
+		}
+	}
+	return results, cobra.ShellCompDirectiveNoSpace
+}
+
+type ResourceServiceCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ResourceServiceCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if services, _, err := resources.Services(s.Client, -1); err == nil {
+			var results []string
+			for _, service := range services {
+				if strings.HasPrefix(service.Name, toComplete) {
+					results = append(results, service.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ResourcePlanCompletion struct {
+	Service    string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ResourcePlanCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		var results []string
+		if services, _, err := resources.Plans(s.Client, c.Service, -1); err == nil {
+			for _, service := range services {
+				results = append(results, service.Name)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ResourceCreateCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ResourceCreateCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 1 {
+		resourceServiceCompletion := ResourceServiceCompletion{ArgsLen: -1, ConfigFile: c.ConfigFile}
+		return resourceServiceCompletion.CompletionFunc(cmd, args, toComplete)
+	} else if len(args) == 2 {
+		resourcePlanCompletion := ResourcePlanCompletion{Service: args[1], ArgsLen: -1, ConfigFile: c.ConfigFile}
+		return resourcePlanCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ResourceCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ResourceCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if resources, _, err := resources.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, resource := range resources {
+				if strings.HasPrefix(resource.Name, toComplete) {
+					results = append(results, resource.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ResourceUpdateCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ResourceUpdateCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		ResourceCompletion := ResourceCompletion{AppID: c.AppID, ArgsLen: -1, ConfigFile: c.ConfigFile}
+		return ResourceCompletion.CompletionFunc(cmd, args, toComplete)
+	} else if len(args) == 1 {
+		resourceServiceCompletion := ResourceServiceCompletion{ArgsLen: -1, ConfigFile: c.ConfigFile}
+		return resourceServiceCompletion.CompletionFunc(cmd, args, toComplete)
+	} else if len(args) == 2 {
+		resourcePlanCompletion := ResourcePlanCompletion{Service: args[1], ArgsLen: -1, ConfigFile: c.ConfigFile}
+		return resourcePlanCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ReleaseCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ReleaseCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if releases, _, err := releases.List(s.Client, appID, "", -1); err == nil {
+			var results []string
+			toComplete = strings.TrimPrefix(toComplete, "v")
+			for _, release := range releases {
+				version := strconv.Itoa(release.Version)
+				if strings.HasPrefix(version, toComplete) {
+					results = append(results, version)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type RouteKindCompletion struct {
+	AppID   *string
+	ArgsLen int
+}
+
+func (c *RouteKindCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	kinds := []string{"HTTPRoute", "TCPRoute", "UDPRoute", "GRPCRoute", "TLSRoute"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, kind := range kinds {
+			if strings.HasPrefix(kind, toComplete) {
+				results = append(results, kind)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type RouteCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *RouteCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if routes, _, err := routes.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, route := range routes {
+				if strings.HasPrefix(route.Name, toComplete) {
+					results = append(results, route.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type ServiceCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *ServiceCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if services, err := services.List(s.Client, appID); err == nil {
+			var results []string
+			for _, service := range services {
+				if strings.HasPrefix(service.Ptype, toComplete) {
+					results = append(results, service.Ptype)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type TagCompletion struct {
+	AppID      *string
+	Ptype      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *TagCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if config, err := config.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for tag := range config.Tags[*c.Ptype] {
+				if strings.HasPrefix(tag, toComplete) {
+					if !slices.Contains(args, tag) {
+						results = append(results, tag)
+					}
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type TlsActionCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *TlsActionCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	actions := []string{"enable", "disable"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, action := range actions {
+			if strings.HasPrefix(action, toComplete) {
+				results = append(results, action)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type TokenCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *TokenCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if s, err := settings.Load(*c.ConfigFile); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if tokens, _, err := tokens.List(s.Client, -1); err == nil {
+			var results []string
+			for _, token := range tokens {
+				if strings.HasPrefix(token.UUID, toComplete) {
+					results = append(results, token.UUID)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type VolumeCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *VolumeCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if appID, s, err := utils.LoadAppSettings(*c.ConfigFile, *c.AppID); err == nil && (c.ArgsLen < 0 || len(args) == c.ArgsLen) {
+		if volumes, _, err := volumes.List(s.Client, appID, -1); err == nil {
+			var results []string
+			for _, volume := range volumes {
+				if strings.HasPrefix(volume.Name, toComplete) {
+					results = append(results, volume.Name)
+				}
+			}
+			return results, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type VolumeTypeCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *VolumeTypeCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	vts := []string{"csi", "nfs", "oss"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, vt := range vts {
+			if strings.HasPrefix(vt, toComplete) {
+				results = append(results, vt)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
+
+type VolumesMountCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *VolumesMountCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		volumeCompletion := VolumeCompletion{AppID: c.AppID, ArgsLen: 0, ConfigFile: c.ConfigFile}
+		return volumeCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		ptsSetArgsCompletion := PtsSetArgsCompletion{
+			PtsCompletion: &PtsCompletion{AppID: c.AppID, ArgsLen: -1, ConfigFile: c.ConfigFile},
+		}
+		return ptsSetArgsCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type VolumesUnmountCompletion struct {
+	AppID      *string
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *VolumesUnmountCompletion) CompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	if len(args) == 0 {
+		volumeCompletion := VolumeCompletion{AppID: c.AppID, ArgsLen: 0, ConfigFile: c.ConfigFile}
+		return volumeCompletion.CompletionFunc(cmd, args, toComplete)
+	} else {
+		ptsArgCompletion := PtsArgsCompletion{
+			PtsCompletion: &PtsCompletion{AppID: c.AppID, ArgsLen: -1, ConfigFile: c.ConfigFile},
+		}
+		return ptsArgCompletion.CompletionFunc(cmd, args, toComplete)
+	}
+}
+
+type VolumesCmdCompletion struct {
+	ArgsLen    int
+	ConfigFile *string
+}
+
+func (c *VolumesCmdCompletion) CompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	cmds := []string{"ls", "cp", "rm"}
+	if c.ArgsLen < 0 || len(args) == c.ArgsLen {
+
+		var results []string
+		for _, cmd := range cmds {
+			if strings.HasPrefix(cmd, toComplete) {
+				results = append(results, cmd)
+			}
+		}
+		return results, cobra.ShellCompDirectiveNoFileComp
+
+	}
+	return nil, cobra.ShellCompDirectiveNoFileComp
+}
diff --git a/internal/parser/apps.go b/internal/parser/apps.go
new file mode 100644
index 000000000..1b587c278
--- /dev/null
+++ b/internal/parser/apps.go
@@ -0,0 +1,222 @@
+package parser
+
+import (
+	"strings"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewAppsCommand creates the apps command
+func NewAppsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "apps",
+		Short: i18n.T("Manage applications used to provide services"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppsList(limit)
+		},
+	}
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("Maximum number of results to display"))
+
+	cmd.AddCommand(appsCreate(cmdr))
+	cmd.AddCommand(appsList(cmdr))
+	cmd.AddCommand(appsInfo(cmdr))
+	cmd.AddCommand(appsOpen(cmdr))
+	cmd.AddCommand(appsLogs(cmdr))
+	cmd.AddCommand(appsRun(cmdr))
+	cmd.AddCommand(appsDestroy(cmdr))
+	cmd.AddCommand(appsTransfer(cmdr))
+	return cmd
+}
+
+// AppsCreate creates the apps create command
+func appsCreate(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		remote   string
+		noRemote bool
+	}
+	cmd := &cobra.Command{
+		Use:   "create [<id>]",
+		Args:  cobra.MaximumNArgs(1),
+		Short: i18n.T("Create a new application"),
+		Long:  i18n.T(`Creates a new application. If no <id> is provided, one will be generated automatically`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			id := ""
+			if len(args) > 0 {
+				id = args[0]
+			}
+			return cmdr.AppCreate(id, flags.remote, flags.noRemote)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.remote, "remote", "r", "drycc", i18n.T("Name of remote to create"))
+	cmd.Flags().BoolVar(&flags.noRemote, "no-remote", false, i18n.T("Do not create a 'drycc' git remote"))
+	return cmd
+}
+
+// AppsList creates the apps list command
+func appsList(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List accessible applications"),
+		Long:  i18n.T("Lists applications visible to the current user"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppsList(limit)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("Maximum number of results to display"))
+	return cmd
+}
+
+// AppsInfo creates the apps info command
+func appsInfo(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: i18n.T("View info about an application"),
+		Long:  i18n.T("Prints info about the current application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppInfo(app)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+// AppsOpen creates the apps open command
+func appsOpen(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "open",
+		Short: i18n.T("Open the application in a browser"),
+		Long:  i18n.T("Opens a URL to the application in the default browser"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppOpen(app)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+// AppsLogs creates the apps logs command
+func appsLogs(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		lines   int
+		follow  bool
+		timeout int
+	}
+
+	cmd := &cobra.Command{
+		Use:   "logs",
+		Short: i18n.T("Retrieve application log events"),
+		Long:  i18n.T("Retrieves the most recent log events"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppLogs(app, flags.lines, flags.follow, flags.timeout)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&flags.lines, "lines", "n", 300, i18n.T("The number of lines to display"))
+	cmd.Flags().BoolVarP(&flags.follow, "follow", "f", false, i18n.T("Specify if the logs should be streamed"))
+	cmd.Flags().IntVarP(&flags.timeout, "timeout", "t", 300, i18n.T("The max seconds of followz the log stream"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+// AppsRun creates the apps run command
+func appsRun(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		timeout uint32
+		expires uint32
+		mounts  []string
+	}
+
+	cmd := &cobra.Command{
+		Use:  "run [--mount=<volume>:<path>...] -- <command>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc apps run --mount=myvolume:/data -- 'echo hello'",
+			map[string]string{
+				"<volume>":  i18n.T("The volume name"),
+				"<path>":    i18n.T("The filesystem path"),
+				"<command>": i18n.T("The shell command to run inside the container"),
+			},
+		),
+		Short: i18n.T("Run a command in an ephemeral app container"),
+		Long:  i18n.T("Runs a command inside an ephemeral app container"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			command := strings.Join(args, " ")
+			return cmdr.AppRun(app, command, flags.mounts, flags.timeout, flags.expires)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().StringSliceVarP(&flags.mounts, "mount", "m", nil, i18n.T("Volume mounts in format 'volume:path'"))
+	cmd.Flags().Uint32VarP(&flags.timeout, "timeout", "t", 3600, i18n.T("Command execution timeout in seconds"))
+	cmd.Flags().Uint32VarP(&flags.expires, "expires", "e", 3600, i18n.T("Retention time of records in seconds"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+// AppsDestroy creates the apps destroy command
+func appsDestroy(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		confirm string
+	}
+
+	cmd := &cobra.Command{
+		Use:     "destroy",
+		Example: "drycc apps destroy -a <app> --confirm <app>",
+		Short:   i18n.T("Destroy an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AppDestroy(app, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T("Skips the prompt for the application name. \n<app> is the uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+// AppsTransfer creates the apps transfer command
+func appsTransfer(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "transfer <username>",
+		Args:  cobra.ExactArgs(1),
+		Short: i18n.T("Transfer app ownership to another user"),
+		Long:  i18n.T("Transfer application ownership to another user."),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			user := args[0]
+			return cmdr.AppTransfer(app, user)
+		},
+	}
+
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
diff --git a/internal/parser/auth.go b/internal/parser/auth.go
new file mode 100644
index 000000000..8f29f7d46
--- /dev/null
+++ b/internal/parser/auth.go
@@ -0,0 +1,80 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewAuthCommand creates the auth command
+func NewAuthCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "auth",
+		Short: i18n.T("Manage authentication"),
+		RunE:  nil,
+	}
+	cmd.AddCommand(authLogin(cmdr))
+	cmd.AddCommand(authLogout(cmdr))
+	cmd.AddCommand(authWhoami(cmdr))
+	return cmd
+}
+
+// AuthLogin creates the auth:login command
+func authLogin(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		username  string
+		password  string
+		sslVerify bool
+	}
+
+	cmd := &cobra.Command{
+		Use:     "login <controller>",
+		Args:    cobra.ExactArgs(1),
+		Example: "drycc auth login http://drycc.local3.dryccapp.com/",
+		Short:   i18n.T("Authenticate against a controller"),
+		Long:    i18n.T("Logs in by authenticating against a controller"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			controller := args[0]
+			return cmdr.Login(controller, flags.sslVerify, flags.username, flags.password)
+		},
+	}
+	cmd.Flags().StringVarP(&flags.username, "username", "u", "", i18n.T("Provide a username for the account"))
+	cmd.Flags().StringVarP(&flags.password, "password", "p", "", i18n.T("Provide a password for the account"))
+	cmd.Flags().BoolVar(&flags.sslVerify, "ssl-verify", true, i18n.T("Enables or disables SSL certificate verification for API requests"))
+	cmd.Flags().SortFlags = false
+	return cmd
+}
+
+// AuthLogout creates the auth:logout command
+func authLogout(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "logout",
+		Example: "drycc auth logout",
+		Short:   i18n.T("Clear the current user session"),
+		Long:    i18n.T("Logs out from a controller and clears the user session"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.Logout()
+		},
+	}
+	return cmd
+}
+
+// AuthWhoami creates the auth:whoami command
+func authWhoami(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		all bool
+	}
+
+	cmd := &cobra.Command{
+		Use:     "whoami",
+		Example: "drycc auth whoami",
+		Short:   i18n.T("Display the current user"),
+		Long:    i18n.T("Displays the currently logged in user"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.Whoami(flags.all)
+		},
+	}
+
+	cmd.Flags().BoolVarP(&flags.all, "all", "a", false, i18n.T("Fetch detailed user information"))
+	return cmd
+}
diff --git a/internal/parser/autodeploy.go b/internal/parser/autodeploy.go
new file mode 100644
index 000000000..ff4139e5e
--- /dev/null
+++ b/internal/parser/autodeploy.go
@@ -0,0 +1,65 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewAutodeployCommand creates the autodeploy command
+func NewAutodeployCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "autodeploy",
+		Short: i18n.T("Manage autodeploy if or not for applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutodeployInfo(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(autodeployInfo(cmdr))
+	cmd.AddCommand(autodeployEnable(cmdr))
+	cmd.AddCommand(autodeployDisable(cmdr))
+	return cmd
+}
+
+// AutodeployInfo Information about the autodeploy
+func autodeployInfo(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: i18n.T("Prints info about the current application's autodeploy if or not"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutodeployInfo(app)
+		},
+	}
+	return cmd
+}
+
+// AutodeployEnable creates the autodeploy enable command
+func autodeployEnable(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "enable",
+		Short: i18n.T("Enables autodeploy for an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutodeployEnable(app)
+		},
+	}
+	return cmd
+}
+
+// AutodeployDisable creates the autodeploy disable command
+func autodeployDisable(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "disable",
+		Short: i18n.T("Disables autodeploy for an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutodeployDisable(app)
+		},
+	}
+	return cmd
+}
diff --git a/internal/parser/autorollback.go b/internal/parser/autorollback.go
new file mode 100644
index 000000000..d168b514e
--- /dev/null
+++ b/internal/parser/autorollback.go
@@ -0,0 +1,64 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewAutorollbackCommand creates the autorollback command
+func NewAutorollbackCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "autorollback",
+		Short: i18n.T("Manage autorollback if or not for application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutorollbackInfo(app)
+		},
+	}
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(autorollbackInfo(cmdr))
+	cmd.AddCommand(autorollbackEnable(cmdr))
+	cmd.AddCommand(autorollbackDisable(cmdr))
+	return cmd
+}
+
+// AutorollbackInfo creates the autorollback info command
+func autorollbackInfo(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: i18n.T("View autorollback info for an application"),
+		Long:  i18n.T("Prints info about the current application's autorollback if or not"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutorollbackInfo(app)
+		},
+	}
+	return cmd
+}
+
+// AutorollbackEnable creates the autorollback enable command
+func autorollbackEnable(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "enable",
+		Short: i18n.T("Enable autorollback for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutorollbackEnable(app)
+		},
+	}
+	return cmd
+}
+
+// AutorollbackDisable creates the autorollback disable command
+func autorollbackDisable(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "disable",
+		Short: i18n.T("Disable autorollback for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutorollbackDisable(app)
+		},
+	}
+	return cmd
+}
diff --git a/internal/parser/autoscale.go b/internal/parser/autoscale.go
new file mode 100644
index 000000000..40cbade7e
--- /dev/null
+++ b/internal/parser/autoscale.go
@@ -0,0 +1,109 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewAutoscaleCommand creates the autoscale command
+func NewAutoscaleCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "autoscale",
+		Short: i18n.T("Manage autoscale for applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutoscaleList(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(autoscaleListCommand(cmdr))
+	cmd.AddCommand(autoscaleSetCommand(cmdr))
+	cmd.AddCommand(autoscaleUnsetCommand(cmdr))
+	return cmd
+}
+
+func autoscaleListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List autoscale options for an application"),
+		Long:  i18n.T("Prints a list of autoscale options for the application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.AutoscaleList(app)
+		},
+	}
+	return cmd
+}
+
+func autoscaleSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		min        int
+		max        int
+		cpuPercent int
+	}
+
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+
+	cmd := &cobra.Command{
+		Use: "set <ptype> --min=<min> --max=<max> --cpu-percent=<percent>",
+		Example: template.CustomExample(
+			"drycc autoscale set web --min=1 --max=10 --cpu-percent=50",
+			map[string]string{
+				"<ptype>": i18n.T("The process type to add to the application's autoscale settings"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Turn on autoscale for an app"),
+		Long:              i18n.T("Set autoscale option per process type for an app"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptype := args[0]
+			return cmdr.AutoscaleSet(app, ptype, flags.min, flags.max, flags.cpuPercent)
+		},
+	}
+
+	cmd.Flags().IntVarP(&flags.min, "min", "", 0, i18n.T("minimum replicas to keep around"))
+	cmd.Flags().IntVarP(&flags.max, "max", "", 0, i18n.T("Max replicas to scale up to"))
+	cmd.Flags().IntVarP(&flags.cpuPercent, "cpu-percent", "", 0, i18n.T("Target CPU utilization percentage"))
+	cmd.Flags().SortFlags = false
+
+	must_flags := []string{"min", "max", "cpu-percent"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	return cmd
+}
+
+func autoscaleUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use: "unset <ptype>",
+		Example: template.CustomExample(
+			"drycc autoscale unset web",
+			map[string]string{
+				"<ptype>": i18n.T("The process type to remove from the application's autoscale settings"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Turn off autoscale for an app"),
+		Long:              i18n.T("Unset autoscale per process type for an app"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptype := args[0]
+			return cmdr.AutoscaleUnset(app, ptype)
+		},
+	}
+	return cmd
+}
diff --git a/internal/parser/builds.go b/internal/parser/builds.go
new file mode 100644
index 000000000..212e9d145
--- /dev/null
+++ b/internal/parser/builds.go
@@ -0,0 +1,114 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewBuildsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "builds",
+		Short: i18n.T("Manage builds created using 'git push'"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.BuildsInfo(app, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the build info needs to be displayed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(buildsInfo(cmdr))
+	cmd.AddCommand(buildsCreate(cmdr))
+	cmd.AddCommand(buildsFetch(cmdr))
+
+	return cmd
+}
+
+func buildsCreate(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		stack     string
+		procfile  string
+		dryccPath string
+		confirm   string
+	}
+	cmd := &cobra.Command{
+		Use:   `create <image>`,
+		Args:  cobra.ExactArgs(1),
+		Short: i18n.T("imports an image and deploys as a new release"),
+		Long: i18n.T(`Creates a new build of an application. Imports an <image> and deploys it to Drycc
+as a new release. If a Procfile or drycc.yaml is present in the current directory,
+it will be used as the default for this application.`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			image := args[0]
+			err := cmdr.BuildsCreate(app, image, flags.stack, flags.procfile, flags.dryccPath, flags.confirm)
+			return err
+		},
+	}
+
+	// // shortcuts pull has not app flag
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().StringVarP(&flags.stack, "stack", "s", "container", i18n.T("The stack name for the application"))
+	cmd.Flags().StringVarP(&flags.procfile, "procfile", "p", "Procfile", i18n.T("A YAML file used to supply a Procfile to the application"))
+	cmd.Flags().StringVarP(&flags.dryccPath, "dryccpath", "d", ".drycc", i18n.T("Drycc config path to the application"))
+	cmd.Flags().StringVarP(&flags.confirm, "confirm", "", "", i18n.T(`To proceed, type "yes"`))
+	cmd.Flags().SortFlags = false
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	return cmd
+}
+
+// buildsInfo
+func buildsInfo(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "info",
+		Example: "drycc builds info",
+		Short:   i18n.T("Print information about a specific build"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.BuildsInfo(app, version)
+		},
+	}
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the build info needs to be displayed"))
+
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+// buildsFetch
+func buildsFetch(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		stack     string
+		procfile  string
+		dryccPath string
+		confirm   string
+		save      bool
+	}
+
+	cmd := &cobra.Command{
+		Use:     "fetch",
+		Example: "drycc builds fetch",
+		Short:   i18n.T("Print process info about a specific build"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.BuildsFetch(app, version, flags.procfile, flags.dryccPath, flags.confirm, flags.save)
+		},
+	}
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the build info needs to be displayed"))
+	cmd.Flags().StringVarP(&flags.procfile, "procfile", "p", "Procfile", i18n.T("A YAML file used to supply a Procfile to the application"))
+	cmd.Flags().StringVarP(&flags.dryccPath, "dryccpath", "d", ".drycc", i18n.T("Drycc config path to the application"))
+	cmd.Flags().StringVarP(&flags.confirm, "confirm", "", "", i18n.T(`To proceed, type "yes"`))
+	cmd.Flags().BoolVarP(&flags.save, "save", "", false, i18n.T("Save process info to the local"))
+	cmd.Flags().SortFlags = false
+
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
diff --git a/internal/parser/certs.go b/internal/parser/certs.go
new file mode 100644
index 000000000..8856b96e3
--- /dev/null
+++ b/internal/parser/certs.go
@@ -0,0 +1,170 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewCertsCommand creates the certs command
+func NewCertsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "certs",
+		Short: i18n.T("Manage SSL endpoints for an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.CertsList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(certsListCommand(cmdr))
+	cmd.AddCommand(certAddCommand(cmdr))
+	cmd.AddCommand(certRemoveCommand(cmdr))
+	cmd.AddCommand(certInfoCommand(cmdr))
+	cmd.AddCommand(certAttachCommand(cmdr))
+	cmd.AddCommand(certDetachCommand(cmdr))
+	return cmd
+}
+
+func certsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List SSL certificates for an application"),
+		Long:  i18n.T("Show certificate information for an SSL application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.CertsList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func certAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use: "add <name> <cert> <key>",
+		Example: template.CustomExample(
+			"drycc certs add cert-name default.crt default.key",
+			map[string]string{
+				"<name>": i18n.T("Name of the certificate to reference it by"),
+				"<cert>": i18n.T("The public key of the SSL certificate"),
+				"<key>":  i18n.T("The private key of the SSL certificate"),
+			},
+		),
+		Args:  cobra.ExactArgs(3),
+		Short: i18n.T("Add an SSL certificate to an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			cert := args[1]
+			key := args[2]
+			return cmdr.CertAdd(app, cert, key, name)
+		},
+	}
+
+	return cmd
+}
+
+func certRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	certCompletion := completion.CertCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "remove <name>",
+		Example: template.CustomExample(
+			"drycc certs remove cert-name",
+			map[string]string{
+				"<name>": i18n.T("The name of the cert to remove from the app"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Remove an SSL certificate from an app"),
+		ValidArgsFunction: certCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.CertRemove(app, name)
+		},
+	}
+
+	return cmd
+}
+
+func certInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	certCompletion := completion.CertCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "info <name>",
+		Example: template.CustomExample(
+			"drycc certs info cert-name",
+			map[string]string{
+				"<name>": i18n.T("The name of the cert to get information from"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Get detailed informaton about the certificate"),
+		Long:              i18n.T("Fetch more detailed information about a certificate"),
+		ValidArgsFunction: certCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.CertInfo(app, name)
+		},
+	}
+
+	return cmd
+}
+
+func certAttachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	certTachCompletion := completion.CertDomainTachCompletion{AppID: &app, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "attach <name> <domain>",
+		Example: template.CustomExample(
+			"drycc certs attach cert-name id.example.com",
+			map[string]string{
+				"<name>":   i18n.T("Name of the certificate to attach domain to"),
+				"<domain>": i18n.T("Common name of the domain to attach to (needs to already be in the system)"),
+			},
+		),
+		Args:              cobra.ExactArgs(2),
+		Short:             i18n.T("Attach an SSL certificate to a domain"),
+		Long:              i18n.T("Attach a certificate to a domain"),
+		ValidArgsFunction: certTachCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			domain := args[1]
+			return cmdr.CertAttach(app, name, domain)
+		},
+	}
+
+	return cmd
+}
+
+func certDetachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	certTachCompletion := completion.CertDomainTachCompletion{AppID: &app, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "detach <name> <domain>",
+		Example: template.CustomExample(
+			"drycc certs attach cert-name id.example.com",
+			map[string]string{
+				"<name>":   i18n.T("Name of the certificate to deatch from a domain"),
+				"<domain>": i18n.T("Common name of the domain to detach from"),
+			},
+		),
+		Args:              cobra.ExactArgs(2),
+		Short:             i18n.T("Detach an SSL certificate from a domain"),
+		Long:              i18n.T("Detach certificate from a domain"),
+		ValidArgsFunction: certTachCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			domain := args[1]
+			return cmdr.CertDetach(app, name, domain)
+		},
+	}
+	return cmd
+}
diff --git a/internal/parser/config.go b/internal/parser/config.go
new file mode 100644
index 000000000..ed03b9dcb
--- /dev/null
+++ b/internal/parser/config.go
@@ -0,0 +1,257 @@
+package parser
+
+import (
+	"strings"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+var configFlags struct {
+	ptype string
+	group string
+}
+
+// NewConfigCommand creates the config command
+func NewConfigCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cobra.EnableCommandSorting = false
+	cmd := &cobra.Command{
+		Use:   "config",
+		Short: i18n.T("Manage environment variables that define app config"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigInfo(app, configFlags.ptype, configFlags.group, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be listed"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be listed"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the config needs to be listed"))
+
+	ptsCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptsCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(configInfoCommand(cmdr))
+	cmd.AddCommand(configSetCommand(cmdr))
+	cmd.AddCommand(configUnsetCommand(cmdr))
+	cmd.AddCommand(configPullCommand(cmdr))
+	cmd.AddCommand(configPushCommand(cmdr))
+	cmd.AddCommand(configAttachCommand(cmdr))
+	cmd.AddCommand(configDetachCommand(cmdr))
+	return cmd
+}
+
+func configInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: i18n.T("An app config info"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigInfo(app, configFlags.ptype, configFlags.group, version)
+		},
+	}
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be listed"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be listed"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the config needs to be listed"))
+
+	ptsCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptsCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+func configSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		group   string
+		confirm string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "set <key>=<value>...",
+		Args:  cobra.MinimumNArgs(1),
+		Short: i18n.T("Set environment variables for an app"),
+		Long:  i18n.T("Sets environment variables for an application or config group"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigSet(app, configFlags.ptype, configFlags.group, args, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be set"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be set"))
+	cmd.Flags().StringVarP(&flags.confirm, "confirm", "", "", i18n.T("To proceed, type 'yes'"))
+	cmd.Flags().SortFlags = false
+	cmd.MarkFlagsMutuallyExclusive("ptype", "group")
+
+	ptsCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptsCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func configUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		confirm string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "unset <key>...",
+		Args:  cobra.MinimumNArgs(1),
+		Short: i18n.T("Unset environment variables for an app"),
+		Long:  i18n.T("Unsets an environment variable for an application or config group"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigUnset(app, configFlags.ptype, configFlags.group, args, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be unset"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be unset"))
+	cmd.Flags().StringVarP(&flags.confirm, "confirm", "", "", i18n.T("To proceed, type 'yes'"))
+	cmd.Flags().SortFlags = false
+	cmd.MarkFlagsMutuallyExclusive("ptype", "group")
+
+	ptsCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptsCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func configPullCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		path        string
+		interactive bool
+		overwrite   bool
+	}
+
+	cmd := &cobra.Command{
+		Use:   "pull",
+		Short: i18n.T("Pull environment variables to the path"),
+		Long: i18n.T(`Extract all environment variables from an application or config group. for local use.
+
+The environmental variables can be piped into a file, 'drycc config:pull > file',
+or stored locally in a file named .env. This file can be
+read by foreman to load the local environment for your app.`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigPull(app, configFlags.ptype, configFlags.group, flags.path, flags.interactive, flags.overwrite)
+		},
+	}
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be pull"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the config needs to be pull"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be pull"))
+	cmd.Flags().StringVar(&flags.path, "path", ".env", i18n.T("A path leading to an environment file"))
+	cmd.Flags().BoolVarP(&flags.interactive, "interactive", "i", false, i18n.T("Prompts for each value to be overwritten"))
+	cmd.Flags().BoolVarP(&flags.overwrite, "overwrite", "o", false, i18n.T("Allows you to have the pull overwrite keys to the path"))
+	cmd.Flags().SortFlags = false
+	cmd.MarkFlagsMutuallyExclusive("ptype", "group")
+
+	ptsCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptsCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func configPushCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		path    string
+		confirm string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "push",
+		Short: i18n.T("Push environment variables from the path"),
+		Long: i18n.T(`Sets environment variables for an application or config group.
+
+This file can be read by foreman
+to load the local environment for your app. The file should be piped via
+stdin, 'drycc config:push < .env', or using the --path option.`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ConfigPush(app, configFlags.ptype, configFlags.group, flags.path, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be push"))
+	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be push"))
+	cmd.Flags().StringVar(&flags.path, "path", ".env", i18n.T("A path leading to an environment file"))
+	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T("To proceed, type 'yes'"))
+	cmd.Flags().SortFlags = false
+	cmd.MarkFlagsMutuallyExclusive("ptype", "group")
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	configGroupCompletion := completion.ConfigGroupCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("group", configGroupCompletion.CompletionFunc)
+	return cmd
+}
+
+func configAttachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	configPtsGroupArgsCompletion := completion.ConfigPtsGroupArgsCompletion{
+		AppID:      &app,
+		ConfigFile: &cmdr.ConfigFile,
+	}
+	cmd := &cobra.Command{
+		Use: "attach <ptype> <group>...",
+		Example: template.CustomExample(
+			"drycc config attach web group1 group2",
+			map[string]string{
+				"<ptype>": i18n.T("The ptype that requires attach configurations"),
+				"<group>": i18n.T("The group that requires attach configurations"),
+			},
+		),
+		Args:              cobra.MinimumNArgs(2),
+		ValidArgsFunction: configPtsGroupArgsCompletion.CompletionFunc,
+		Short:             i18n.T("Selects environment groups to attach an app ptype"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			configFlags.ptype = args[0]
+			return cmdr.ConfigAttach(app, configFlags.ptype, strings.Join(args[1:], ","))
+		},
+	}
+
+	return cmd
+}
+
+func configDetachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	configPtsGroupArgsCompletion := completion.ConfigPtsGroupArgsCompletion{
+		AppID:      &app,
+		ConfigFile: &cmdr.ConfigFile,
+	}
+	cmd := &cobra.Command{
+		Use:  "detach <ptype> <group>...",
+		Args: cobra.MinimumNArgs(2),
+		Example: template.CustomExample(
+			"drycc config detach web group1 group2",
+			map[string]string{
+				"<ptype>": i18n.T("The ptype that requires detach configurations"),
+				"<group>": i18n.T("The group that requires detach configurations"),
+			},
+		),
+		Short:             i18n.T("Selects environment groups to detach an app ptype"),
+		ValidArgsFunction: configPtsGroupArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			configFlags.ptype = args[0]
+			return cmdr.ConfigDetach(app, configFlags.ptype, strings.Join(args[1:], ","))
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/domains.go b/internal/parser/domains.go
new file mode 100644
index 000000000..393229345
--- /dev/null
+++ b/internal/parser/domains.go
@@ -0,0 +1,83 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewDomainsCommand creates the domains command
+func NewDomainsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "domains",
+		Short: i18n.T("Manage and assign domain names to your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.DomainsList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(domainsListCommand(cmdr))
+	cmd.AddCommand(domainsAddCommand(cmdr))
+	cmd.AddCommand(domainsRemoveCommand(cmdr))
+	return cmd
+}
+
+func domainsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List domains bound to an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.DomainsList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func domainsAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+	cmd := &cobra.Command{
+		Use:   "add <domain>",
+		Args:  cobra.ExactArgs(1),
+		Short: i18n.T("Bind a domain to an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			domain := args[0]
+			return cmdr.DomainsAdd(app, domain, flags.ptype)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The ptype for domain"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	return cmd
+}
+
+func domainsRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	domainCompletion := completion.DomainCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:               "remove <domain>",
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Unbinds a domain for an application"),
+		ValidArgsFunction: domainCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			domain := args[0]
+			return cmdr.DomainsRemove(app, domain)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/flags.go b/internal/parser/flags.go
new file mode 100644
index 000000000..af43df267
--- /dev/null
+++ b/internal/parser/flags.go
@@ -0,0 +1,5 @@
+package parser
+
+var app string
+var version int
+var limit int
diff --git a/internal/parser/gateways.go b/internal/parser/gateways.go
new file mode 100644
index 000000000..5ce3aef49
--- /dev/null
+++ b/internal/parser/gateways.go
@@ -0,0 +1,111 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewGatewaysCommand creates the gateways command
+func NewGatewaysCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "gateways",
+		Short: i18n.T("Manage gateways for your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.GatewaysList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(gatewaysList(cmdr))
+	cmd.AddCommand(gatewaysAdd(cmdr))
+	cmd.AddCommand(gatewaysRemove(cmdr))
+	return cmd
+}
+
+func gatewaysList(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List application gateways"),
+		Long:  i18n.T("Lists gateways for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.GatewaysList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func gatewaysAdd(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name     string
+		port     int
+		protocol string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "add <name>",
+		Args:  cobra.ExactArgs(1),
+		Short: i18n.T("Create gateways for an application"),
+		Long:  i18n.T("Creates gateways for an application and binds it to allow listener of the main app domain"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.GatewaysAdd(app, name, flags.port, flags.protocol)
+		},
+	}
+
+	cmd.Flags().IntVar(&flags.port, "port", 0, i18n.T("Port is the network port, the listener expects to receive"))
+	cmd.Flags().StringVarP(&flags.protocol, "protocol", "", "", i18n.T("Protocol specifies the network protocol this listener expects to receive. Supports TCP, UDP, TLS, HTTP, and HTTPS"))
+
+	must_flags := []string{"port", "protocol"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	gatewayProtocolCompletion := completion.GatewayProtocolCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("protocol", gatewayProtocolCompletion.CompletionFunc)
+	return cmd
+}
+
+func gatewaysRemove(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name     string
+		port     int
+		protocol string
+	}
+
+	gatewayNameCompletion := completion.GatewayNameCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:               "remove <name> --port=<port> --protocol=<protocol>",
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Remove gateways from an application"),
+		Long:              i18n.T("Deletes specific gateway for application"),
+		ValidArgsFunction: gatewayNameCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.GatewaysRemove(app, name, flags.port, flags.protocol)
+		},
+	}
+
+	cmd.Flags().IntVar(&flags.port, "port", 0, i18n.T("Port is the network port, the listener received"))
+	cmd.Flags().StringVarP(&flags.protocol, "protocol", "", "", i18n.T("Protocol specifies the network protocol this listener received"))
+
+	must_flags := []string{"port", "protocol"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	gatewayProtocolCompletion := completion.GatewayProtocolCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("protocol", gatewayProtocolCompletion.CompletionFunc)
+	return cmd
+}
diff --git a/internal/parser/git.go b/internal/parser/git.go
new file mode 100644
index 000000000..b9d6571bc
--- /dev/null
+++ b/internal/parser/git.go
@@ -0,0 +1,58 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewGitCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "git",
+		Short: i18n.T("Manage git for applications"),
+		RunE:  nil,
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(gitRemote(cmdr))
+	cmd.AddCommand(gitRemove(cmdr))
+	return cmd
+}
+
+func gitRemote(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app    string
+		remote string
+		force  bool
+	}
+
+	cmd := &cobra.Command{
+		Use:   "remote",
+		Short: i18n.T("Adds git remote of application to repository"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.GitRemote(app, flags.remote, flags.force)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.remote, "remote", "r", "drycc", i18n.T("Name of remote to create"))
+	cmd.Flags().BoolVarP(&flags.force, "force", "f", false, i18n.T("Overwrite remote of the given name if it already exists"))
+	cmd.Flags().SortFlags = false
+
+	return cmd
+}
+
+func gitRemove(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "remove",
+		Short: i18n.T("Removes git remote of application from repository"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.GitRemove(app)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/healthchecks.go b/internal/parser/healthchecks.go
new file mode 100644
index 000000000..d07f45839
--- /dev/null
+++ b/internal/parser/healthchecks.go
@@ -0,0 +1,263 @@
+package parser
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/drycc/controller-sdk-go/api"
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+var healthchecksFlags struct {
+	ptype string
+}
+
+func NewHealthchecksCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "healthchecks",
+		Short: i18n.T("Manage application healthchecks"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.HealthchecksList(app, healthchecksFlags.ptype, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name of the application"))
+	cmd.PersistentFlags().StringVarP(&healthchecksFlags.ptype, "ptype", "p", "web", i18n.T("The ptype for which the health check needs to be removed"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the health check needs to be listed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(healthchecksList(cmdr))
+	cmd.AddCommand(healthchecksSet(cmdr))
+	cmd.AddCommand(healthchecksUnset(cmdr))
+	return cmd
+}
+
+func healthchecksList(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List healthchecks for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.HealthchecksList(app, healthchecksFlags.ptype, version)
+		},
+	}
+
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the health check needs to be listed"))
+
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+func healthchecksSet(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype         string
+		healthType    string
+		probeType     string
+		path          string
+		port          int
+		headers       string
+		initialDelay  int
+		timeout       int
+		period        int
+		successThresh int
+		failureThresh int
+		commandArgs   []string
+	}
+
+	healthChecksCompletion := completion.HealthChecksCompletion{ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "set <health-type> <probe-type> [--] <args>...",
+		Args: cobra.MinimumNArgs(2),
+		Example: template.CustomExample(
+			"drycc healthchecks set readinessProbe httpGet --path=/health -- 8000",
+			map[string]string{
+				"<health-type>": i18n.T("The healthcheck type, such as 'startupProbe' 'livenessProbe' or 'readinessProbe'"),
+				"<probe-type>":  i18n.T("the healthcheck probe type, such as 'httpGet', 'exec' or 'tcpSocket'"),
+				"<args>": i18n.T(`The arguments required for the healthcheck probe. 'exec', accepts a list of arguments;
+                  'httpGet' and 'tcpSocket' accept a port number.`),
+			},
+		),
+		Short: i18n.T("Set healthchecks for an application"),
+		Long: i18n.T(`Sets healthchecks for an application.
+
+By default, Workflow only checks that the application starts in their Container. A health
+check may be added by configuring a health check probe for the application. The health
+checks are implemented as Kubernetes Container Probes. A 'startupProbe' 'livenessProbe' 
+and a 'readinessProbe' can be configured, and each probe can be of type 'httpGet', 'exec' 
+or 'tcpSocket' depending on the type of probe the Container requires.
+
+A 'startupProbe' indicates whether the application within the container is started.
+All other probes are disabled if a startup probe is provided, until it succeeds.
+If the startup probe fails, the container is subjected to its restart policy.
+
+A 'livenessProbe' is useful for applications running for long periods of time, eventually
+transitioning to broken states and cannot recover except by restarting them.
+
+Other times, a 'readinessProbe' is useful when the Container is only temporarily unable
+to serve, and will recover on its own. In this case, if a Container fails its 'readinessProbe'
+, the Container will not be shut down, but rather the Container will stop receiving
+incoming requests.
+
+'httpGet' probes are just as it sounds: it performs a HTTP GET operation on the Container.
+A response code inside the 200-399 range is considered a pass. 'httpGet' probes accept a
+port number to perform the HTTP GET operation on the Container.
+
+'exec' probes run a command inside the Container to determine its health. An exit code of
+zero is considered a pass, while a non-zero status code is considered a fail. 'exec'
+probes accept a string of arguments to be run inside the Container.
+
+'tcpSocket' probes attempt to open a socket in the Container. The Container is only
+considered healthy if the check can establish a connection. 'tcpSocket' probes accept a
+port number to perform the socket connection on the Container.
+`),
+		ValidArgsFunction: healthChecksCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			healthType := args[0]
+			probeType := args[1]
+
+			probe := &api.Healthcheck{
+				InitialDelaySeconds: flags.initialDelay,
+				TimeoutSeconds:      flags.timeout,
+				PeriodSeconds:       flags.period,
+				SuccessThreshold:    flags.successThresh,
+				FailureThreshold:    flags.failureThresh,
+			}
+
+			switch probeType {
+			case "httpGet":
+				headers := []string{}
+				if flags.headers != "" {
+					headers = strings.Split(flags.headers, ",")
+				}
+				parsedHeaders, err := parseHeaders(headers)
+				if err != nil {
+					return fmt.Errorf("could not parse headers: %s", err)
+				}
+
+				port, err := strconv.Atoi(args[2])
+				if err != nil {
+					return fmt.Errorf("could not parse port: %s", err)
+				}
+				probe.HTTPGet = &api.HTTPGetProbe{
+					Path:        flags.path,
+					Port:        port,
+					HTTPHeaders: parsedHeaders,
+				}
+			case "exec":
+				probeArgs := args[2:]
+				probe.Exec = &api.ExecProbe{
+					Command: probeArgs,
+				}
+			case "tcpSocket":
+				port, err := strconv.Atoi(args[2])
+				if err != nil {
+					return fmt.Errorf("could not parse port: %s", err)
+				}
+
+				probe.TCPSocket = &api.TCPSocketProbe{
+					Port: port,
+				}
+			default:
+				return fmt.Errorf("invalid probe type %s, Must be one of: \"httpGet\", \"exec\", \"tcpSocket\"", probeType)
+			}
+
+			return cmdr.HealthchecksSet(app, healthType, healthchecksFlags.ptype, probe)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.path, "path", "/", i18n.T("The relative URL path for 'httpGet' probes"))
+	cmd.Flags().StringVar(&flags.headers, "headers", "", i18n.T("The HTTP headers to send for 'httpGet' probes, separated by commas"))
+	cmd.Flags().IntVar(&flags.initialDelay, "initial-delay-timeout", 50, i18n.T("The initial delay timeout for the probe"))
+	cmd.Flags().IntVar(&flags.timeout, "timeout-seconds", 50, i18n.T("The number of seconds after which the probe times out"))
+	cmd.Flags().IntVar(&flags.period, "period-seconds", 10, i18n.T("How often (in seconds) to perform the probe"))
+	cmd.Flags().IntVar(&flags.successThresh, "success-threshold", 1, i18n.T("Minimum consecutive successes for the probe to be considered successful after having failed"))
+	cmd.Flags().IntVar(&flags.failureThresh, "failure-threshold", 3, i18n.T("Minimum consecutive successes for the probe to be considered failed after having succeeded"))
+	cmd.Flags().SortFlags = false
+
+	return cmd
+}
+
+func healthchecksUnset(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		ptype   string
+		healths []string
+	}
+
+	healthTypeCompletion := completion.HealthTypeCompletion{ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "unset <health-type>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc healthchecks unset startupProbe",
+			map[string]string{
+				"<health-type>": i18n.T("the healthcheck type, such as 'startupProbe' 'livenessProbe' or 'readinessProbe'"),
+			},
+		),
+		Short:             i18n.T("Unset healthchecks for an application"),
+		ValidArgsFunction: healthTypeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.healths = args
+			for healthcheck := range flags.healths {
+				if err := checkProbeType(flags.healths[healthcheck]); err != nil {
+					return err
+				}
+			}
+			return cmdr.HealthchecksUnset(app, healthchecksFlags.ptype, flags.healths)
+		},
+	}
+	return cmd
+}
+
+func parseHeaders(headers []string) ([]*api.KVPair, error) {
+	var parsedHeaders []*api.KVPair
+	for _, header := range headers {
+		parsedHeader, err := parseHeader(header)
+		if err != nil {
+			return nil, err
+		}
+		parsedHeaders = append(parsedHeaders, parsedHeader)
+	}
+	return parsedHeaders, nil
+}
+
+func parseHeader(header string) (*api.KVPair, error) {
+	headerParts := strings.SplitN(header, ":", 2)
+	if len(headerParts) != 2 {
+		return nil, fmt.Errorf("could not find separator in header (%s)", header)
+	}
+	return &api.KVPair{
+		Name:  strings.TrimSpace(headerParts[0]),
+		Value: strings.TrimSpace(headerParts[1]),
+	}, nil
+}
+
+func checkProbeType(probe string) error {
+	var found bool
+	probeTypes := []string{
+		"startupProbe",
+		"livenessProbe",
+		"readinessProbe",
+	}
+	for _, probeType := range probeTypes {
+		if probe == probeType {
+			found = true
+		}
+	}
+	if !found {
+		return fmt.Errorf("probe type %s is invalid. Must be one of %s", probe, probeTypes)
+	}
+	return nil
+}
diff --git a/internal/parser/keys.go b/internal/parser/keys.go
new file mode 100644
index 000000000..762dd45f8
--- /dev/null
+++ b/internal/parser/keys.go
@@ -0,0 +1,90 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewKeysCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "keys",
+		Short: i18n.T("Manage ssh keys used for 'git push' deployments"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.KeysList(results)
+		},
+	}
+	cmd.AddCommand(keysListCommand(cmdr))
+	cmd.AddCommand(keyAddCommand(cmdr))
+	cmd.AddCommand(keyRemoveCommand(cmdr))
+	return cmd
+}
+
+func keysListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List SSH keys for the logged in user"),
+
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.KeysList(results)
+		},
+	}
+
+	return cmd
+}
+
+func keyAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name string
+		key  string
+	}
+
+	cmd := &cobra.Command{
+		Use: "add [<name>] [<key>]",
+		Example: template.CustomExample(
+			"drycc keys add mykey /path/to/public.key",
+			map[string]string{
+				"<name>": i18n.T("Name of the SSH key"),
+				"<key>":  i18n.T("A local file path to an SSH public key used to push application code"),
+			},
+		),
+		Args:  cobra.MaximumNArgs(2),
+		Short: i18n.T("Add an SSH key"),
+		Long:  i18n.T("Adds SSH keys for the logged in user"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if len(args) == 1 {
+				flags.key = args[0]
+			} else if len(args) == 2 {
+				flags.name = args[0]
+				flags.key = args[1]
+			}
+			return cmdr.KeyAdd(flags.name, flags.key)
+		},
+	}
+
+	return cmd
+}
+
+func keyRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		key string
+	}
+	keyCompletion := completion.KeyCompletion{ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:               "remove <key>",
+		Args:              cobra.ExactArgs(1),
+		Short:             i18n.T("Remove an SSH key"),
+		Long:              i18n.T("Removes an SSH key for the logged in user"),
+		ValidArgsFunction: keyCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.key = args[0]
+			return cmdr.KeyRemove(flags.key)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/labels.go b/internal/parser/labels.go
new file mode 100644
index 000000000..f5d6ac1f3
--- /dev/null
+++ b/internal/parser/labels.go
@@ -0,0 +1,99 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewLabelsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "labels",
+		Short: i18n.T("Manage labels of application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.LabelsList(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(labelsListCommand(cmdr))
+	cmd.AddCommand(labelsSetCommand(cmdr))
+	cmd.AddCommand(labelsUnsetCommand(cmdr))
+	return cmd
+}
+
+func labelsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List application's labels"),
+		Long:  i18n.T("Prints a list of labels of the application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.LabelsList(app)
+		},
+	}
+
+	return cmd
+}
+
+func labelsSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app  string
+		tags []string
+	}
+
+	cmd := &cobra.Command{
+		Use:  "set <key>=<value>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc labels set FOO=bar",
+			map[string]string{
+				"<key>":   i18n.T(`the label key, for example: "git_repo" or "team"`),
+				"<value>": i18n.T(`the label value, for example: "https://github.com/drycc/workflow" or "frontend"`),
+			},
+		),
+		Short: i18n.T("Add new application's label"),
+		Long: i18n.T(`Sets labels for an application.
+
+A label is a key/value pair used to label an application. This label is a general information for drycc user.
+Mostly used for administration/maintenance information, note for application. This information isn't send to scheduler.`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.tags = args
+			return cmdr.LabelsSet(app, flags.tags)
+		},
+	}
+
+	return cmd
+}
+
+func labelsUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app  string
+		keys []string
+	}
+	labelCompletion := completion.LabelCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "unset <key>...",
+		Example: template.CustomExample(
+			"drycc labels unset FOO",
+			map[string]string{
+				"<key>": i18n.T(`the label key to unset, for example: "git_repo" or "team"`),
+			},
+		),
+		Args: cobra.MinimumNArgs(1),
+
+		Short:             i18n.T("Remove application's label"),
+		Long:              i18n.T("Unsets labels for an application"),
+		ValidArgsFunction: labelCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.keys = args
+			return cmdr.LabelsUnset(app, flags.keys)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/limits.go b/internal/parser/limits.go
new file mode 100644
index 000000000..6a9bc4112
--- /dev/null
+++ b/internal/parser/limits.go
@@ -0,0 +1,157 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewLimitsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "limits",
+		Short: i18n.T("Manage resource limits for your application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.LimitsList(app, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the limit needs to be listed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(limitsListCommand(cmdr))
+	cmd.AddCommand(limitSetCommand(cmdr))
+	cmd.AddCommand(limitUnsetCommand(cmdr))
+	cmd.AddCommand(limitSpecsCommand(cmdr))
+	cmd.AddCommand(limitPlansCommand(cmdr))
+	return cmd
+}
+
+func limitsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "list",
+		Example: "drycc limits list",
+		Short:   i18n.T("List resource limits for an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.LimitsList(app, version)
+		},
+	}
+
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the limit needs to be listed"))
+
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func limitSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		limits []string
+	}
+	limitSetPlanCompletion := completion.LimitSetPlanCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "set <ptype>=<value>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc limits set web=std1.large.c1m2",
+			map[string]string{
+				"<ptype>": i18n.T("The process type as defined in your Procfile, such as 'web' or 'worker'."),
+				"<value>": i18n.T("The limit plan id to apply to the process type"),
+			},
+		),
+		Short: i18n.T("Set resource limits for an app"),
+		Long: i18n.T(`Sets resource limits for an application.
+
+A resource limit is a finite resource within a pod which we can apply
+restrictions through Kubernetes.`),
+		ValidArgsFunction: limitSetPlanCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.limits = args
+			return cmdr.LimitsSet(app, flags.limits)
+		},
+	}
+
+	return cmd
+}
+
+func limitUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		limits []string
+	}
+	ptsCompletion := completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "unset <ptype>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc limits unset web",
+			map[string]string{
+				"<ptype>": i18n.T("The process type as defined in your Procfile, such as 'web' or 'worker'"),
+			},
+		),
+		Short:             i18n.T("Unset resource limits for an app"),
+		ValidArgsFunction: ptsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.limits = args
+			return cmdr.LimitsUnset(app, flags.limits)
+		},
+	}
+
+	return cmd
+}
+
+func limitSpecsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		keywords string
+		limit    int
+	}
+
+	cmd := &cobra.Command{
+		Use:   "specs",
+		Short: i18n.T("List specification information of the server"),
+		Long:  i18n.T("List all available limit specs"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.LimitsSpecs(flags.keywords, results)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.keywords, "keywords", "k", "", i18n.T("Search keywords separated by commas, matching must satisfy all of the specified."))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+	return cmd
+}
+
+func limitPlansCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		specID string
+		cpu    int
+		memory int
+		limit  int
+	}
+
+	cmd := &cobra.Command{
+		Use:   "plans",
+		Short: i18n.T("List all available limit plans"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.LimitsPlans(flags.specID, flags.cpu, flags.memory, results)
+		},
+	}
+
+	cmd.Flags().IntVar(&flags.cpu, "cpu", 0, i18n.T("Query plans that meet the specified number of cpu cores"))
+	cmd.Flags().IntVarP(&flags.memory, "memory", "m", 0, i18n.T("Query plans that meet the specified memory capacity, unit GiB"))
+	cmd.Flags().StringVar(&flags.specID, "spec-id", "", i18n.T("Query plans that meet the specified spec id, see [specs] subcommand."))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+	cmd.Flags().SortFlags = false
+
+	completion := completion.LimitSpecCompletion{ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("spec-id", completion.CompletionFunc)
+
+	return cmd
+}
diff --git a/internal/parser/perms.go b/internal/parser/perms.go
new file mode 100644
index 000000000..8a9dab7fe
--- /dev/null
+++ b/internal/parser/perms.go
@@ -0,0 +1,136 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewPermsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "perms",
+		Short: i18n.T("Manage permissions for applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.PermList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(permsListCommand(cmdr))
+	cmd.AddCommand(permAddCommand(cmdr))
+	cmd.AddCommand(permUpdateCommand(cmdr))
+	cmd.AddCommand(permRemoveCommand(cmdr))
+	return cmd
+}
+
+func permsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List all user permissions"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.PermList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func permAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app         string
+		username    string
+		permissions string
+	}
+
+	userPermsArgsCompletion := completion.UserPermsArgsCompletion{
+		UserPermsCompletion: &completion.UserPermsCompletion{ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:  "add <username> <permission>...",
+		Args: cobra.MinimumNArgs(2),
+		Example: template.CustomExample(
+			"drycc add username view change delete",
+			map[string]string{
+				"<username>":   i18n.T("The name of the user"),
+				"<permission>": i18n.T("The user permissions (view,change,delete)"),
+			},
+		),
+		Short:             i18n.T("Add a user permissions"),
+		ValidArgsFunction: userPermsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.username = args[0]
+			flags.permissions = args[1]
+			return cmdr.PermCreate(app, flags.username, flags.permissions)
+		},
+	}
+
+	return cmd
+}
+
+func permUpdateCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app         string
+		username    string
+		permissions string
+	}
+
+	permUpdateCompletion := completion.PermUpdateCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "update <username> <permission>... ",
+		Args: cobra.MinimumNArgs(2),
+		Example: template.CustomExample(
+			"drycc update username view,change",
+			map[string]string{
+				"<username>":   i18n.T("The name of the user"),
+				"<permission>": i18n.T("The user's permissions (view,change,delete)"),
+			},
+		),
+		Short:             i18n.T("Update a user permission"),
+		ValidArgsFunction: permUpdateCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.username = args[0]
+			flags.permissions = args[1]
+			return cmdr.PermUpdate(app, flags.username, flags.permissions)
+		},
+	}
+
+	return cmd
+}
+
+func permRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app      string
+		username string
+	}
+	permUsernameCompletion := completion.PermUsernameCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "remove <username>",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc perms remove username",
+			map[string]string{
+				"<username>": i18n.T("The name of the user"),
+			},
+		),
+		Short:             i18n.T("Remove a user permissions"),
+		ValidArgsFunction: permUsernameCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+
+			flags.username = args[0]
+			return cmdr.PermDelete(app, flags.username)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/ps.go b/internal/parser/ps.go
new file mode 100644
index 000000000..c221315ed
--- /dev/null
+++ b/internal/parser/ps.go
@@ -0,0 +1,163 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewPsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "ps",
+		Short: i18n.T("Manage processes inside an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.PsList(app, 1000)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(psListCommand(cmdr))
+	cmd.AddCommand(psLogsCommand(cmdr))
+	cmd.AddCommand(psExecCommand(cmdr))
+	cmd.AddCommand(psDescribeCommand(cmdr))
+	cmd.AddCommand(psDeleteCommand(cmdr))
+	return cmd
+}
+
+func psListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List application pods"),
+		Long:  i18n.T("Lists processes servicing an application"),
+		Args:  cobra.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.PsList(app, 1000)
+		},
+	}
+
+	return cmd
+}
+
+func psLogsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app       string
+		lines     int
+		follow    bool
+		container string
+		previous  bool
+	}
+
+	psCompletion := completion.PsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+
+	cmd := &cobra.Command{
+		Use:               "logs <pod>",
+		Args:              cobra.ExactArgs(1),
+		Example:           "drycc ps logs my-pod",
+		Short:             i18n.T("Print the logs for a container"),
+		Long:              i18n.T("Print the logs for a container in a pod or specified resource"),
+		ValidArgsFunction: psCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			podID := args[0]
+			if flags.lines < 0 {
+				flags.lines = -1
+			}
+			return cmdr.PsLogs(app, podID, flags.lines, flags.follow, flags.container, flags.previous)
+		},
+	}
+
+	cmd.Flags().IntVarP(&flags.lines, "lines", "l", 300, i18n.T("The number of lines to display, -1 showing all log lines"))
+	cmd.Flags().BoolVarP(&flags.follow, "follow", "f", false, i18n.T("Specify if the logs should be streamed"))
+	cmd.Flags().StringVar(&flags.container, "container", "", i18n.T("Print the logs of this container"))
+	cmd.Flags().BoolVarP(&flags.previous, "previous", "p", false, i18n.T("Print the logs for the previous instance of the container in a pod if it exists"))
+	cmd.Flags().SortFlags = false
+
+	return cmd
+}
+
+func psExecCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		pod     string
+		command []string
+		tty     bool
+		stdin   bool
+	}
+	psCompletion := completion.PsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "exec <pod> [flags] -- <command>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc ps exec my-pod -it -- bash",
+			map[string]string{
+				"<pod>": i18n.T("The pod name for the application"),
+			},
+		),
+		Short:             i18n.T("Execute a command in a container"),
+		ValidArgsFunction: psCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.pod = args[0]
+			flags.command = args[1:]
+			return cmdr.PsExec(app, flags.pod, flags.tty, flags.stdin, flags.command)
+		},
+	}
+
+	// shortcuts exec has not app flag
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().BoolVarP(&flags.tty, "tty", "t", false, i18n.T("Stdin is a TTY"))
+	cmd.Flags().BoolVarP(&flags.stdin, "stdin", "i", false, i18n.T("Pass stdin to the container"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func psDescribeCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	psCompletion := completion.PsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "describe <pod>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc ps describe my-pod",
+			map[string]string{
+				"<pod>": i18n.T("The pod name for the application"),
+			},
+		),
+		Short:             i18n.T("Print a detailed description of the selected process"),
+		ValidArgsFunction: psCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			podID := args[0]
+			return cmdr.PsDescribe(app, podID)
+		},
+	}
+
+	return cmd
+}
+
+func psDeleteCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	psCompletion := completion.PsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "delete <pod>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc ps delete my-pod another-pod",
+			map[string]string{
+				"<pod>": i18n.T("The pod name for the application"),
+			},
+		),
+		Short:             i18n.T("Delete the selected processes"),
+		ValidArgsFunction: psCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.PsDelete(app, args)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/pts.go b/internal/parser/pts.go
new file mode 100644
index 000000000..7ee9e51b4
--- /dev/null
+++ b/internal/parser/pts.go
@@ -0,0 +1,164 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewPtsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "pts",
+		Short: i18n.T("Manage process types inside an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.PtsList(app, 1000)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(ptsListCommand(cmdr))
+	cmd.AddCommand(ptsDescribeCommand(cmdr))
+	cmd.AddCommand(ptsRestartCommand(cmdr))
+	cmd.AddCommand(ptsScaleCommand(cmdr))
+	cmd.AddCommand(ptsCleanCommand(cmdr))
+	return cmd
+}
+
+// ptsListCommand
+func ptsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List application process types"),
+		Long:  i18n.T("Lists process types servicing an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.PtsList(app, 1000)
+		},
+	}
+
+	return cmd
+}
+
+// ptsDescribeCommand
+func ptsDescribeCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:  "describe <ptype>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc pts describe web",
+			map[string]string{
+				"<ptype>": i18n.T("The process name as defined in your Procfile"),
+			},
+		),
+		Short:             i18n.T("Describe a process type"),
+		Long:              i18n.T("Print a detailed description of the selected process type"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.ptype = args[0]
+			return cmdr.PtsDescribe(app, flags.ptype)
+		},
+	}
+
+	return cmd
+}
+
+// ptsRestartCommand
+func ptsRestartCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptypes  []string
+		confirm string
+	}
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:               "restart [<ptype>...]",
+		Args:              cobra.ArbitraryArgs,
+		Example:           "drycc pts restart web",
+		Short:             i18n.T("Restart application process types"),
+		Long:              i18n.T("Restart an application or process types"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.ptypes = args
+			return cmdr.PtsRestart(app, flags.ptypes, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T(`To proceed, type "yes"`))
+
+	return cmd
+}
+
+// ptsScaleCommand
+func ptsScaleCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app   string
+		scale []string // format like "web=5"
+	}
+
+	ptsSetArgsCompletion := completion.PtsSetArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:  "scale <ptype>=<num>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc pts scale web=3",
+			map[string]string{
+				"<ptype>": i18n.T("The process name as defined in your Procfile"),
+				"<num>":   i18n.T("The number of processes"),
+			},
+		),
+		Short:             i18n.T("Scale process types of replicas"),
+		Long:              i18n.T("Scales an application's processes by type"),
+		ValidArgsFunction: ptsSetArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.scale = args
+			return cmdr.PtsScale(app, flags.scale)
+		},
+	}
+
+	// shortcuts scale has not app flag
+	cmd.Flags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	return cmd
+}
+
+// ptsCleanCommand
+func ptsCleanCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptypes []string
+	}
+
+	cmd := &cobra.Command{
+		Use:  "clean <ptype>...",
+		Args: cobra.MinimumNArgs(1),
+		Example: template.CustomExample(
+			"drycc pts clean web",
+			map[string]string{
+				"<ptype>": i18n.T("The process name as defined in your Procfile"),
+			},
+		),
+		Short: i18n.T("Clean process types of not used"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.ptypes = args
+			return cmdr.PtsClean(app, flags.ptypes)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/registry.go b/internal/parser/registry.go
new file mode 100644
index 000000000..4c10317e0
--- /dev/null
+++ b/internal/parser/registry.go
@@ -0,0 +1,111 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// no default web
+var registryFlags struct {
+	ptype string
+}
+
+func NewRegistryCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "registry",
+		Short: i18n.T("Manage private registry information for your application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RegistryList(app, registryFlags.ptype, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().StringVarP(&registryFlags.ptype, "ptype", "p", "", i18n.T("The ptype for registry"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the registry needs to be listed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+
+	cmd.AddCommand(registryListCommand(cmdr))
+	cmd.AddCommand(registrySetCommand(cmdr))
+	cmd.AddCommand(registryUnsetCommand(cmdr))
+	return cmd
+}
+
+func registryListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List registry info for an app"),
+		Long:  i18n.T("Lists registry information for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RegistryList(app, registryFlags.ptype, version)
+		},
+	}
+
+	cmd.Flags().StringVarP(&registryFlags.ptype, "ptype", "p", "", i18n.T("The ptype for registry"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the registry needs to be listed"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+func registrySetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	// default web
+	var flags struct {
+		ptype string
+	}
+	cmd := &cobra.Command{
+		Use:  "set <username> <password>",
+		Args: cobra.ExactArgs(2),
+		Example: template.CustomExample(
+			"drycc registry set username password",
+			map[string]string{
+				"<username>": i18n.T("The username of the registry"),
+				"<password>": i18n.T("The password of the registry"),
+			},
+		),
+		Short: i18n.T("Set registry info for an app"),
+		Long: i18n.T(`Sets registry information for an application. These credentials are the same as those used for
+'podmain login' to the private registry.`),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			username := args[0]
+			password := args[1]
+			return cmdr.RegistrySet(app, flags.ptype, username, password)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The ptype for registry"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	return cmd
+}
+
+func registryUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	// default web
+	var flags struct {
+		ptype string
+	}
+	cmd := &cobra.Command{
+		Use:   "unset",
+		Short: i18n.T("Unset registry info for an app"),
+		Long:  i18n.T("Unsets registry information for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RegistryUnset(app, flags.ptype)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The ptype for registry"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	return cmd
+}
diff --git a/internal/parser/releases.go b/internal/parser/releases.go
new file mode 100644
index 000000000..35bb8c394
--- /dev/null
+++ b/internal/parser/releases.go
@@ -0,0 +1,170 @@
+package parser
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewReleasesCommand creates the releases command
+func NewReleasesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+
+	cmd := &cobra.Command{
+		Use:               "releases [<ptype>...]",
+		Example:           "drycc releases ptype1 ptype2",
+		Short:             i18n.T("Manage releases of an application"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ReleasesList(app, args, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(releasesListCommand(cmdr))
+	cmd.AddCommand(releasesInfoCommand(cmdr))
+	cmd.AddCommand(releasesDeployCommand(cmdr))
+	cmd.AddCommand(releasesRollbackCommand(cmdr))
+	return cmd
+}
+
+func releasesListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+
+	cmd := &cobra.Command{
+		Use:               "list [<ptype>...]",
+		Example:           "drycc releases list ptype1 ptype2",
+		Short:             i18n.T("List an application's release history"),
+		Long:              i18n.T("Lists release history for an application"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ReleasesList(app, args, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+	cmd.Flags().SortFlags = false
+
+	return cmd
+}
+
+func releasesInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	releaseCompletion := completion.ReleaseCompletion{AppID: &app, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "info <version>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc releases info 1",
+			map[string]string{
+				"<version>": i18n.T(`The release of the application, such as '1'`),
+			},
+		),
+		Short:             i18n.T("Print information about a specific release"),
+		Long:              i18n.T("Prints info about a particular release"),
+		ValidArgsFunction: releaseCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			versionStr := args[0]
+			version, err := strconv.Atoi(strings.TrimPrefix(versionStr, "v"))
+			if err != nil {
+				return fmt.Errorf("invalid version format: %v", err)
+			}
+			return cmdr.ReleasesInfo(app, version)
+		},
+	}
+
+	return cmd
+}
+
+func releasesDeployCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		force   bool
+		confirm string
+	}
+
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+
+	cmd := &cobra.Command{
+		Use:               "deploy [<ptype>...]",
+		Short:             i18n.T("Deploy the latest release by process types"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptypes := args
+			return cmdr.ReleasesDeploy(app, ptypes, flags.force, flags.confirm)
+		},
+	}
+
+	cmd.Flags().BoolVarP(&flags.force, "force", "f", false, i18n.T("Force deploy"))
+	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T(`To proceed, type "yes"`))
+	cmd.Flags().SortFlags = false
+
+	return cmd
+}
+
+func releasesRollbackCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use: "rollback [<ptype>...] [version]",
+		Example: template.CustomExample(
+			"drycc releases rollback web 2",
+			map[string]string{
+				"<ptype>":   i18n.T(`The process name as defined in your Procfile, such as 'web'`),
+				"<version>": i18n.T(`The release of the application, such as '1'`),
+			},
+		),
+		Short: i18n.T("Return to a previous release"),
+		Long:  i18n.T("Rolls back to a previous application release"),
+		Args:  cobra.MinimumNArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			// Handle arguments safely
+			var ptypes []string
+			var versionStr string
+
+			if len(args) == 1 {
+				versionStr = args[1]
+			} else {
+				ptypes = args[:len(args)-1]
+				versionStr = args[len(args)-1]
+			}
+			version, err := versionFromString(versionStr)
+
+			if err != nil {
+				return err
+			}
+			return cmdr.ReleasesRollback(app, ptypes, version)
+		},
+	}
+
+	return cmd
+}
+
+func versionFromString(version string) (int, error) {
+	if version[:1] == "v" {
+		if len(version) < 2 {
+			return -1, fmt.Errorf("%s is not in the form 'v#'", version)
+		}
+
+		return strconv.Atoi(version[1:])
+	}
+
+	return strconv.Atoi(version)
+}
diff --git a/internal/parser/resources.go b/internal/parser/resources.go
new file mode 100644
index 000000000..61f99b4d5
--- /dev/null
+++ b/internal/parser/resources.go
@@ -0,0 +1,276 @@
+package parser
+
+import (
+	"fmt"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewResourcesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "resources",
+		Short: i18n.T("Manage resources for your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ResourcesList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(resourcesServicesCommand(cmdr))
+	cmd.AddCommand(resourcesPlansCommand(cmdr))
+	cmd.AddCommand(resourcesCreateCommand(cmdr))
+	cmd.AddCommand(resourcesListCommand(cmdr))
+	cmd.AddCommand(resourcesDescribeCommand(cmdr))
+	cmd.AddCommand(resourcesUpdateCommand(cmdr))
+	cmd.AddCommand(resourcesBindCommand(cmdr))
+	cmd.AddCommand(resourcesUnbindCommand(cmdr))
+	cmd.AddCommand(resourcesDestroyCommand(cmdr))
+	return cmd
+}
+
+func resourcesServicesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "services",
+		Short: i18n.T("List all available resource services"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ResourcesServices(results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func resourcesPlansCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	resourceServiceCompletion := completion.ResourceServiceCompletion{ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "plans <service>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc resources plans redis",
+			map[string]string{
+				"<service>": i18n.T("The service name for plans"),
+			},
+		),
+		Short:             i18n.T("List all available plans for a resource service"),
+		ValidArgsFunction: resourceServiceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			service := args[0]
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ResourcesPlans(service, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func resourcesCreateCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		values  string
+		path    string
+		confirm string
+	}
+	resourceCreateCompletion := completion.ResourceCreateCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "create <name> <service> <plan> [<param>=<value>...]",
+		Args: cobra.MinimumNArgs(3),
+		Example: template.CustomExample(
+			"drycc resources create myredis redis standard-128 -f file.yaml",
+			map[string]string{
+				"<name>":    i18n.T("This resource instance alias"),
+				"<service>": i18n.T("The resource's service"),
+				"<plan>":    i18n.T("The service's plan"),
+				"<param>":   i18n.T("The resource instance parameters key"),
+				"<value>":   i18n.T("The resource instance parameters value"),
+			},
+		),
+		Short:             i18n.T("Create a resource for the application"),
+		ValidArgsFunction: resourceCreateCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			plan := fmt.Sprintf("%s:%s", args[1], args[2])
+			params := args[3:]
+
+			if flags.values != "" {
+				params = nil
+			}
+			return cmdr.ResourcesCreate(app, plan, name, params, flags.values)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.values, "values", "f", "", i18n.T("Specify values in a YAML file. If set, params will be discard"))
+
+	return cmd
+}
+
+func resourcesListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List resources in the application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.ResourcesList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func resourcesDescribeCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		name    string
+		details bool
+	}
+	resourceCompletion := completion.ResourceCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "describe <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc resources describe myredis",
+			map[string]string{
+				"<name>": i18n.T("This resource instance alias"),
+			},
+		),
+		Short:             i18n.T("Get a resource's detail in the application"),
+		ValidArgsFunction: resourceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.ResourceGet(app, name, flags.details)
+		},
+	}
+
+	cmd.Flags().BoolVar(&flags.details, "details", false, i18n.T("Show extra details provided to resource"))
+
+	return cmd
+}
+
+func resourcesUpdateCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app    string
+		values string
+		path   string
+	}
+	resourceUpdateCompletion := completion.ResourceUpdateCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "update <name> <service> <plan> [<param>=<value>...]",
+		Args: cobra.MinimumNArgs(3),
+		Example: template.CustomExample(
+			"drycc resources update myredis redis standard-128 -f file.yaml",
+			map[string]string{
+				"<name>":    i18n.T("This resource instance alias"),
+				"<service>": i18n.T("The resource's service"),
+				"<plan>":    i18n.T("The service's plan"),
+				"<param>":   i18n.T("The resource instance parameters key"),
+				"<value>":   i18n.T("The resource instance parameters value"),
+			},
+		),
+		Short:             i18n.T("Update a resource from the application"),
+		ValidArgsFunction: resourceUpdateCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			plan := fmt.Sprintf("%s:%s", args[1], args[2])
+			params := args[2:]
+
+			if flags.values != "" {
+				params = nil
+			}
+
+			return cmdr.ResourcePut(app, plan, name, params, flags.values)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.values, "values", "f", "", i18n.T("Specify values in a YAML file. If set, params will be discard"))
+
+	return cmd
+}
+
+func resourcesDestroyCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app     string
+		name    string
+		confirm string
+	}
+	resourceCompletion := completion.ResourceCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "destroy <name> --confirm=<resource>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc resources destroy myredis",
+			map[string]string{
+				"<name>": i18n.T("The resource instance alias name"),
+			},
+		),
+		Short:             i18n.T("Delete a resource from the application"),
+		ValidArgsFunction: resourceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.ResourceDelete(app, name, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T(`skips the prompt for the resource name. <resource> is the uniquely identifiable
+name for the resource`))
+
+	return cmd
+}
+
+func resourcesBindCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	resourceCompletion := completion.ResourceCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "bind <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc resources bind myredis",
+			map[string]string{
+				"<name>": i18n.T("The resource instance alias name"),
+			},
+		),
+		Short:             i18n.T("Bind a resource for an application"),
+		ValidArgsFunction: resourceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.ResourceBind(app, name)
+		},
+	}
+
+	return cmd
+}
+
+func resourcesUnbindCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	resourceCompletion := completion.ResourceCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "unbind <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc resources unbind myredis",
+			map[string]string{
+				"<name>": i18n.T("The resource instance alias name"),
+			},
+		),
+		Short:             i18n.T("unbind a resources for an application"),
+		ValidArgsFunction: resourceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.ResourceUnbind(app, name)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/routes.go b/internal/parser/routes.go
new file mode 100644
index 000000000..5de21dd93
--- /dev/null
+++ b/internal/parser/routes.go
@@ -0,0 +1,257 @@
+package parser
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/drycc/controller-sdk-go/api"
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewRoutesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "routes",
+		Short: i18n.T("Manage routes for your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.RoutesList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(routesListCommand(cmdr))
+	cmd.AddCommand(routesAddCommand(cmdr))
+	cmd.AddCommand(routesGetCommand(cmdr))
+	cmd.AddCommand(routesSetCommand(cmdr))
+	cmd.AddCommand(routesAttachCommand(cmdr))
+	cmd.AddCommand(routesDetachCommand(cmdr))
+	cmd.AddCommand(routesRemoveCommand(cmdr))
+	return cmd
+}
+
+func routesAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	routeKindCompletion := completion.RouteKindCompletion{ArgsLen: 1}
+	cmd := &cobra.Command{
+		Use:  "add <name> <kind> <backend>...",
+		Args: cobra.MinimumNArgs(3),
+		Example: template.CustomExample(
+			`drycc routes add myroute HTTPRoute svc:8080,100`,
+			map[string]string{
+				"<name>":    i18n.T("The unique name of the route"),
+				"<kind>":    i18n.T("The route kind, range: HTTPRoute,TCPRoute,UDPRoute,GRPCRoute,TLSRoute"),
+				"<backend>": i18n.T(`The route's backend, pattern: <service>:<port>,<weight>`),
+			},
+		),
+		Short:             i18n.T("Create a route for an application"),
+		Long:              i18n.T("Creates routes for an application, provides a way to route requests"),
+		ValidArgsFunction: routeKindCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			kind := args[1]
+			backends := args[2:]
+
+			var backendRefs []api.BackendRefRequest
+			for _, backend := range backends {
+				params := strings.Split(backend, ",")
+				if len(params) != 2 {
+					return fmt.Errorf("invalid backend format %s", backend)
+				}
+				servicePort := strings.Split(params[0], ":")
+				if len(servicePort) != 2 {
+					return fmt.Errorf("backend must be in 'service:port,weight' format")
+				}
+				port, err := strconv.Atoi(servicePort[1])
+				if err != nil {
+					return err
+				}
+				weight, err := strconv.Atoi(params[1])
+				if err != nil {
+					return err
+				}
+				backendRefs = append(backendRefs, api.BackendRefRequest{
+					Kind:   "Service",
+					Name:   servicePort[0],
+					Port:   int32(port),
+					Weight: int32(weight),
+				})
+			}
+
+			return cmdr.RoutesCreate(app, name, kind, backendRefs...)
+		},
+	}
+
+	return cmd
+}
+
+func routesListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List application routes"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.RoutesList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func routesGetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	routeCompletion := completion.RouteCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "get <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc routes get myroute",
+			map[string]string{
+				"<name>": i18n.T("The unique name of the route"),
+			},
+		),
+		Short:             i18n.T("Get route rules"),
+		Long:              i18n.T("Get route rules for an application"),
+		ValidArgsFunction: routeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.RoutesGet(app, name)
+		},
+	}
+
+	return cmd
+}
+
+func routesSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		rulesFile string
+	}
+	routeCompletion := completion.RouteCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "set <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc routes set myroute --rules-file=rules.yaml",
+			map[string]string{
+				"<name>": i18n.T("The unique name of the route"),
+			},
+		),
+		Short:             i18n.T("Set route rules"),
+		Long:              i18n.T("Set route rules for an application"),
+		ValidArgsFunction: routeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.RoutesSet(app, name, flags.rulesFile)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.rulesFile, "rules-file", "", i18n.T("Rules-file is the file name of rule to apply"))
+	cmd.MarkFlagRequired("rules-file")
+
+	return cmd
+}
+
+func routesAttachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		port    int
+		gateway string
+	}
+	routeCompletion := completion.RouteCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "attach <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc routes attach myroute --gateway=gw1 --port=80",
+			map[string]string{
+				"<name>": i18n.T("The unique name of the route"),
+			},
+		),
+		Short:             i18n.T("Attach to gateway"),
+		ValidArgsFunction: routeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.RoutesAttach(app, name, flags.port, flags.gateway)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.gateway, "gateway", "", i18n.T("The unique name of the gaetway"))
+	cmd.Flags().IntVarP(&flags.port, "port", "", 0, i18n.T("Port is the network port, the gateway listener expects to receive"))
+
+	must_flags := []string{"gateway", "port"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	gatewayNameCompletion := completion.GatewayNameCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("gateway", gatewayNameCompletion.CompletionFunc)
+	return cmd
+}
+
+func routesDetachCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		port    int
+		gateway string
+	}
+	routeCompletion := completion.RouteCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "detach <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc routes detach myroute --gateway=gw1 --port=80",
+			map[string]string{
+				"<name>": i18n.T("The unique name of the route"),
+			},
+		),
+		Short:             i18n.T("Dettach to gateway"),
+		ValidArgsFunction: routeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.RoutesDetach(app, name, flags.port, flags.gateway)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.gateway, "gateway", "", i18n.T("The unique name of the gaetway"))
+	cmd.Flags().IntVarP(&flags.port, "port", "", 0, i18n.T("Port is the network port, the gateway listener expects to receive"))
+
+	must_flags := []string{"port", "protocol"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	gatewayNameCompletion := completion.GatewayNameCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("gateway", gatewayNameCompletion.CompletionFunc)
+	return cmd
+}
+
+func routesRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+
+	routeCompletion := completion.RouteCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "remove <name>",
+		Args: cobra.ExactArgs(1),
+		Example: template.CustomExample(
+			"drycc routes remove myroute",
+			map[string]string{
+				"<name>": i18n.T("The unique name of the route"),
+			},
+		),
+		Short:             i18n.T("Remove a route from an application"),
+		ValidArgsFunction: routeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			return cmdr.RoutesRemove(app, name)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/routing.go b/internal/parser/routing.go
new file mode 100644
index 000000000..a5dc82283
--- /dev/null
+++ b/internal/parser/routing.go
@@ -0,0 +1,64 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewRoutingCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "routing",
+		Short: i18n.T("Manage routability of an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RoutingInfo(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(routingInfoCommand(cmdr))
+	cmd.AddCommand(routingEnableCommand(cmdr))
+	cmd.AddCommand(routingDisableCommand(cmdr))
+	return cmd
+}
+
+func routingInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: i18n.T("View application's routability information"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RoutingInfo(app)
+		},
+	}
+
+	return cmd
+}
+
+func routingEnableCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "enable",
+		Short: i18n.T("Enable routing for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RoutingEnable(app)
+		},
+	}
+
+	return cmd
+}
+
+func routingDisableCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "disable",
+		Short: i18n.T("Disable routing for an application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.RoutingDisable(app)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/services.go b/internal/parser/services.go
new file mode 100644
index 000000000..5f5d736db
--- /dev/null
+++ b/internal/parser/services.go
@@ -0,0 +1,121 @@
+package parser
+
+import (
+	"strconv"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewServicesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "services",
+		Short: i18n.T("Manage services for your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ServicesList(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(servicesList(cmdr))
+	cmd.AddCommand(servicesAdd(cmdr))
+	cmd.AddCommand(servicesRemove(cmdr))
+	return cmd
+}
+
+func servicesAdd(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		port     string
+		protocol string
+	}
+
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:  "add <ptype> <port>:<target>",
+		Args: cobra.ExactArgs(2),
+		Example: template.CustomExample(
+			"drycc services add web-new 80:8080",
+			map[string]string{
+				"<ptype>": i18n.T(`procfile type which should handle the request, e.g. webhooks (should be bind to the port PORT)
+             only single extra service per Porcfile type could be created.`),
+				"<port>":   i18n.T("The port that will be exposed by this service"),
+				"<target>": i18n.T("Number or name of the port to access on the pods targeted by the service"),
+			},
+		),
+		Short:             i18n.T("Create service for an application"),
+		Long:              i18n.T("Creates extra service for an application and binds it to specific route of the main app domain"),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptype := args[0]
+			portTarget := args[1]
+			return cmdr.ServicesAdd(app, ptype, portTarget, flags.protocol)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.protocol, "protocol", "TCP", i18n.T("The IP protocol for this port. Supports TCP, UDP, and SCTP"))
+
+	serviceProtocolCompletion := completion.ServiceProtocolCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("protocol", serviceProtocolCompletion.CompletionFunc)
+	return cmd
+}
+
+func servicesList(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "list",
+		Example: "drycc services list",
+		Short:   i18n.T("List application services"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.ServicesList(app)
+		},
+	}
+
+	return cmd
+}
+
+func servicesRemove(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app      string
+		port     int
+		protocol string
+	}
+
+	ServiceCompletion := completion.ServiceCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:  "remove <ptype> <port>",
+		Args: cobra.ExactArgs(2),
+		Example: template.CustomExample(
+			"drycc services remove web-new 80",
+			map[string]string{
+				"<ptype>": i18n.T("procfile type which should handle the request, e.g. webhooks"),
+				"<port>":  i18n.T("The port exposed by this service"),
+			},
+		),
+		Short:             i18n.T("Remove service from an application"),
+		ValidArgsFunction: ServiceCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptype := args[0]
+			port, err := strconv.Atoi(args[1])
+			if err != nil {
+				return err
+			}
+			flags.port = port
+			return cmdr.ServicesRemove(app, ptype, flags.protocol, port)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.protocol, "protocol", "TCP", i18n.T("The IP protocol for this port. Supports TCP, UDP, and SCTP"))
+
+	serviceProtocolCompletion := completion.ServiceProtocolCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("protocol", serviceProtocolCompletion.CompletionFunc)
+	return cmd
+}
diff --git a/internal/parser/shortcuts.go b/internal/parser/shortcuts.go
new file mode 100644
index 000000000..dd6856545
--- /dev/null
+++ b/internal/parser/shortcuts.go
@@ -0,0 +1,87 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+type Shortcuts interface {
+	Create(cmdr *commands.DryccCmd) []*cobra.Command
+}
+
+var SupportedShortcuts = []Shortcuts{
+	&AuthShortcuts{}, &AppsShortcuts{}, &PsShortcuts{}, &BuildsShortcuts{}, &PtsShortcuts{},
+}
+
+type AuthShortcuts struct{}
+
+func (a *AuthShortcuts) Create(cmdr *commands.DryccCmd) []*cobra.Command {
+	login := authLogin(cmdr)
+	login.Example = "drycc login http://drycc.local3.dryccapp.com/"
+
+	logout := authLogout(cmdr)
+	logout.Example = "drycc auth logout"
+
+	whoami := authWhoami(cmdr)
+	whoami.Example = "drycc whoami"
+
+	return []*cobra.Command{login, logout, whoami}
+}
+
+type AppsShortcuts struct{}
+
+func (a *AppsShortcuts) Create(cmdr *commands.DryccCmd) []*cobra.Command {
+	destroy := appsDestroy(cmdr)
+	destroy.Example = "drycc destroy -a <app> --confirm <app>"
+
+	run := appsRun(cmdr)
+	run.Example = template.CustomExample(
+		"drycc run --mount=myvolume:/data -- 'echo hello'",
+		map[string]string{
+			"<volume>":  i18n.T("The volume name"),
+			"<path>":    i18n.T("The filesystem path"),
+			"<command>": i18n.T("The shell command to run inside the container"),
+		},
+	)
+	return []*cobra.Command{appsCreate(cmdr), destroy, appsInfo(cmdr), appsOpen(cmdr), run}
+}
+
+type PsShortcuts struct{}
+
+func (p *PsShortcuts) Create(cmdr *commands.DryccCmd) []*cobra.Command {
+	exec := psExecCommand(cmdr)
+	exec.Example = template.CustomExample(
+		"drycc exec my-pod -it -- bash",
+		map[string]string{
+			"<pod>": i18n.T("The pod name for the application"),
+		},
+	)
+
+	logs := psLogsCommand(cmdr)
+	logs.Example = "drycc logs my-pod"
+	return []*cobra.Command{exec, logs}
+}
+
+type BuildsShortcuts struct{}
+
+func (b *BuildsShortcuts) Create(cmdr *commands.DryccCmd) []*cobra.Command {
+	create := buildsCreate(cmdr)
+	create.Use = `pull <image>`
+	return []*cobra.Command{create}
+}
+
+type PtsShortcuts struct{}
+
+func (p *PtsShortcuts) Create(cmdr *commands.DryccCmd) []*cobra.Command {
+	scale := ptsScaleCommand(cmdr)
+	scale.Example = template.CustomExample(
+		"drycc scale web=3",
+		map[string]string{
+			"<ptype>": i18n.T("The process name as defined in your Procfile"),
+			"<num>":   i18n.T("The number of processes"),
+		},
+	)
+	return []*cobra.Command{scale}
+}
diff --git a/internal/parser/tags.go b/internal/parser/tags.go
new file mode 100644
index 000000000..b13b46156
--- /dev/null
+++ b/internal/parser/tags.go
@@ -0,0 +1,136 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewTagsCommand creates the tags command
+func NewTagsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "tags",
+		Short: i18n.T("Manage tags for application containers"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TagsList(app, flags.ptype, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "", i18n.T("The process name as defined in your Procfile"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the tag needs to be listed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(tagsListCommand(cmdr))
+	cmd.AddCommand(tagsSetCommand(cmdr))
+	cmd.AddCommand(tagsUnsetCommand(cmdr))
+	return cmd
+}
+
+func tagsListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use:               "list",
+		Short:             i18n.T("List tags for an application"),
+		Args:              cobra.MaximumNArgs(1),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TagsList(app, flags.ptype, version)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The process name as defined in your Procfile"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the tag needs to be listed"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+func tagsSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use: "set <key>=<value>...",
+		Example: template.CustomExample(
+			"drycc tags set environ=prod rack=1",
+			map[string]string{
+				"<key>":   i18n.T("The tag key"),
+				"<value>": i18n.T("The tag value"),
+			},
+		),
+		Short: i18n.T("Set tags for an application"),
+		Long: i18n.T(`Sets tags for an application.
+
+A tag is a key/value pair used to tag an application's containers and is passed to the
+scheduler. This is often used to restrict workloads to specific hosts matching the
+scheduler-configured metadata.`),
+		Args:              cobra.MinimumNArgs(1),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			tags := args[0:]
+			return cmdr.TagsSet(app, flags.ptype, tags)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The process name as defined in your Procfile"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+
+	return cmd
+}
+
+func tagsUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		ptype string
+	}
+	tagCompletion := completion.TagCompletion{AppID: &app, Ptype: &flags.ptype, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "unset <key>...",
+		Example: template.CustomExample(
+			"drycc tags unset environ rack",
+			map[string]string{
+				"<key>": i18n.T("The tag key to unset"),
+			},
+		),
+		Short:             i18n.T("Unset tags for an application"),
+		Args:              cobra.MinimumNArgs(1),
+		ValidArgsFunction: tagCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			keys := args[0:]
+			return cmdr.TagsUnset(app, flags.ptype, keys)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.ptype, "ptype", "p", "web", i18n.T("The process name as defined in your Procfile"))
+
+	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
+
+	return cmd
+}
diff --git a/internal/parser/timeouts.go b/internal/parser/timeouts.go
new file mode 100644
index 000000000..25862c5ad
--- /dev/null
+++ b/internal/parser/timeouts.go
@@ -0,0 +1,100 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewTimeoutsCommand creates the timeouts command
+func NewTimeoutsCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "timeouts",
+		Short: i18n.T("Manage pods termination grace period"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TimeoutsList(app, version)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the timeout needs to be listed"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+
+	cmd.AddCommand(timeoutListCommand(cmdr))
+	cmd.AddCommand(timeoutSetCommand(cmdr))
+	cmd.AddCommand(timeoutUnsetCommand(cmdr))
+	return cmd
+}
+
+func timeoutListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List resource timeouts for an app"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TimeoutsList(app, version)
+		},
+	}
+
+	cmd.Flags().IntVarP(&version, "version", "v", 0, i18n.T("The version for which the timeout needs to be listed"))
+
+	releaseCompletion := completion.ReleaseCompletion{ConfigFile: &cmdr.ConfigFile, AppID: &app}
+	cmd.RegisterFlagCompletionFunc("version", releaseCompletion.CompletionFunc)
+	return cmd
+}
+
+func timeoutSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	ptsSetArgsCompletion := completion.PtsSetArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use: "set <ptype>=<value>...",
+		Example: template.CustomExample(
+			"drycc timeouts set web=30 worker=60",
+			map[string]string{
+				"<ptype>": i18n.T("The process type as defined in your Procfile"),
+				"<value>": i18n.T("The value to apply to the process type in seconds"),
+			},
+		),
+		Short:             i18n.T("Set resource timeouts for an app"),
+		Long:              i18n.T("Sets termination grace period for an application"),
+		Args:              cobra.MinimumNArgs(1),
+		ValidArgsFunction: ptsSetArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			timeouts := args
+			return cmdr.TimeoutsSet(app, timeouts)
+		},
+	}
+
+	return cmd
+}
+
+func timeoutUnsetCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	ptsArgsCompletion := completion.PtsArgsCompletion{
+		PtsCompletion: &completion.PtsCompletion{AppID: &app, ArgsLen: -1, ConfigFile: &cmdr.ConfigFile},
+	}
+	cmd := &cobra.Command{
+		Use: "unset <ptype>...",
+		Example: template.CustomExample(
+			"drycc timeouts unset web worker",
+			map[string]string{
+				"<ptype>": i18n.T("The process type as defined in your Procfile"),
+			},
+		),
+		Short:             i18n.T("Unset resource timeouts for an app"),
+		Long:              i18n.T("Unsets timeouts for an application. Default value (30s) or set by drycc controller"),
+		Args:              cobra.MinimumNArgs(1),
+		ValidArgsFunction: ptsArgsCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ptypes := args
+			return cmdr.TimeoutsUnset(app, ptypes)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/parser/tls.go b/internal/parser/tls.go
new file mode 100644
index 000000000..e202edfd9
--- /dev/null
+++ b/internal/parser/tls.go
@@ -0,0 +1,132 @@
+package parser
+
+import (
+	"fmt"
+
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewTLSCommand creates the TLS command
+func NewTLSCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "tls",
+		Short: i18n.T("Manage TLS/SSL settings for applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TLSInfo(app)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("Application name"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(tlsInfoCommand(cmdr))
+	cmd.AddCommand(tlsIssuerCommand(cmdr))
+	cmd.AddCommand(tlsAutoCommand(cmdr))
+	cmd.AddCommand(tlsForceCommand(cmdr))
+	return cmd
+}
+
+func tlsInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "info",
+		Example: "drycc tls info",
+		Short:   i18n.T("View TLS settings information"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TLSInfo(app)
+		},
+	}
+
+	return cmd
+}
+
+func tlsForceCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	tlsActionCompletion := completion.TlsActionCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "force <action>",
+		Example: template.CustomExample(
+			"drycc tls force enable",
+			map[string]string{
+				"<action>": i18n.T("Action to perform, 'enable' or 'disable'"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		ValidArgs:         []string{"enable", "disable"},
+		Short:             i18n.T("Force TLS settings to HTTPS-only redirection"),
+		ValidArgsFunction: tlsActionCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			action := args[0]
+			if action == "enable" {
+				return cmdr.TLSForceEnable(app)
+			} else if action == "disable" {
+				return cmdr.TLSForceDisable(app)
+			}
+			return fmt.Errorf("invalid action: %s, please use 'enable' or 'disable'", action)
+		},
+	}
+
+	return cmd
+}
+
+func tlsAutoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	tlsActionCompletion := completion.TlsActionCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "auto <action>",
+		Example: template.CustomExample(
+			"drycc tls auto enable",
+			map[string]string{
+				"<action>": i18n.T("Action to perform, 'enable' or 'disable'"),
+			},
+		),
+		Args:              cobra.ExactArgs(1),
+		ValidArgs:         []string{"enable", "disable"},
+		Short:             i18n.T("Automatic certificate management"),
+		ValidArgsFunction: tlsActionCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			action := args[0]
+			if action == "enable" {
+				return cmdr.TLSAutoEnable(app)
+			} else if action == "disable" {
+				return cmdr.TLSAutoDisable(app)
+			}
+			return fmt.Errorf("invalid action: %s, please use 'enable' or 'disable'", action)
+		},
+	}
+
+	return cmd
+}
+
+func tlsIssuerCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		app       string
+		email     string
+		server    string
+		keyID     string
+		keySecret string
+	}
+
+	cmd := &cobra.Command{
+		Use:   "issuer",
+		Short: i18n.T("Configure automatic certificate management issuer details"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.TLSAutoIssuer(app, flags.email, flags.server, flags.keyID, flags.keySecret)
+		},
+	}
+
+	cmd.Flags().StringVar(&flags.email, "email", "", i18n.T("ACME account email"))
+	cmd.Flags().StringVar(&flags.server, "server", "", i18n.T("ACME server URL"))
+	cmd.Flags().StringVar(&flags.keyID, "key-id", "", i18n.T("CA key ID"))
+	cmd.Flags().StringVar(&flags.keySecret, "key-secret", "", i18n.T("CA key secret"))
+
+	must_flags := []string{"email", "server", "key-id", "key-secret"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	return cmd
+}
diff --git a/internal/parser/tokens.go b/internal/parser/tokens.go
new file mode 100644
index 000000000..46ca62b7f
--- /dev/null
+++ b/internal/parser/tokens.go
@@ -0,0 +1,106 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewTokensCommand creates the tokens command
+func NewTokensCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "tokens",
+		Short: i18n.T("Manage user tokens"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.TokensList(results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	cmd.AddCommand(tokensListCommand(cmdr))
+	cmd.AddCommand(tokensAddCommand(cmdr))
+	cmd.AddCommand(tokensRemoveCommand(cmdr))
+	return cmd
+}
+
+func tokensListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("Lists tokens visible to the current controller"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.TokensList(results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+	return cmd
+}
+
+func tokensAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		username string
+		password string
+	}
+
+	cmd := &cobra.Command{
+		Use: "add <alias>",
+		Example: template.CustomExample(
+			"drycc tokens add mytoken -u uname -p passwd",
+			map[string]string{
+				"<alias>": i18n.T("Provide a alias for controller authentication token"),
+			},
+		),
+		Short: i18n.T("Add a token for controller authentication"),
+		Args:  cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			alias := args[0]
+			_, err := cmdr.TokensAdd(nil, flags.username, flags.password, alias, "", true)
+			return err
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.username, "username", "u", "", i18n.T("Provide a username for the account"))
+	cmd.Flags().StringVarP(&flags.password, "password", "p", "", i18n.T("Provide a password for the account"))
+
+	must_flags := []string{"username", "password"}
+	for _, must_flag := range must_flags {
+		cmd.MarkFlagRequired(must_flag)
+	}
+
+	return cmd
+}
+
+func tokensRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		id      string
+		confirm string
+	}
+
+	// coTokenCompletion
+	tokenCompletion := completion.TokenCompletion{ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "remove <id>",
+		Example: template.CustomExample(
+			"drycc tokens remove 34103c3b-077e-4e37-a9ad-29ba4324ad8b",
+			map[string]string{
+				"<id>": i18n.T("The id of the token for controller authentication"),
+			},
+		),
+		Short:             i18n.T("Remove a token for controller authentication"),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: tokenCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.id = args[0]
+			return cmdr.TokensRemove(flags.id, flags.confirm)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.confirm, "confirm", "", "", i18n.T(`To proceed, type "yes"`))
+
+	return cmd
+}
diff --git a/internal/parser/update.go b/internal/parser/update.go
new file mode 100644
index 000000000..c59e76919
--- /dev/null
+++ b/internal/parser/update.go
@@ -0,0 +1,22 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+func NewUpdateCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var dryRun bool
+	var cmd = &cobra.Command{
+		Use:   "update",
+		Args:  cobra.NoArgs,
+		Short: i18n.T("Update workflow cli to latest release"),
+		Long:  i18n.T("Update workflow cli to latest release"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmdr.Update(dryRun)
+		},
+	}
+	cmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, i18n.T("Simulate an update, only print the version info."))
+	return cmd
+}
diff --git a/internal/parser/users.go b/internal/parser/users.go
new file mode 100644
index 000000000..4428925ba
--- /dev/null
+++ b/internal/parser/users.go
@@ -0,0 +1,91 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewUsersCommand creates the users command
+func NewUsersCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "users",
+		Short: i18n.T("Manage registered users"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.UsersList(results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	cmd.AddCommand(usersListCommand(cmdr))
+	cmd.AddCommand(usersEnableCommand(cmdr))
+	cmd.AddCommand(usersDisableCommand(cmdr))
+	return cmd
+}
+
+func usersListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List all registered users"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.UsersList(results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+	return cmd
+}
+
+func usersEnableCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		username string
+	}
+
+	cmd := &cobra.Command{
+		Use: "enable <username>",
+		Example: template.CustomExample(
+			"drycc users enable john_doe",
+			map[string]string{
+				"<username>": i18n.T("The username you want to enable"),
+			},
+		),
+		Short: i18n.T("Enable a user"),
+		Long: i18n.T(`Enable a user when his status is disabled.
+Requires admin privileges.`),
+		Args: cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.username = args[0]
+			return cmdr.UsersEnable(flags.username)
+		},
+	}
+	return cmd
+}
+
+func usersDisableCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		username string
+	}
+
+	cmd := &cobra.Command{
+		Use: "disable <username>",
+		Example: template.CustomExample(
+			"drycc users disable john_doe",
+			map[string]string{
+				"<username>": i18n.T("The username you want to disable"),
+			},
+		),
+		Short: i18n.T("Disable a user"),
+		Long: i18n.T(`Disable a user when his status is disabled.
+Requires admin privileges.`),
+		Args: cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.username = args[0]
+			return cmdr.UsersDisable(flags.username)
+		},
+	}
+	return cmd
+}
diff --git a/internal/parser/version.go b/internal/parser/version.go
new file mode 100644
index 000000000..5d4c06838
--- /dev/null
+++ b/internal/parser/version.go
@@ -0,0 +1,24 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// Build an image from source code
+func NewVersionCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		all bool
+	}
+	DryccCmd := &cobra.Command{
+		Use:   "version",
+		Short: i18n.T("Display client version"),
+		Long:  i18n.T("Displays the client version"),
+		Run: func(DryccCmd *cobra.Command, args []string) {
+			cmdr.Version(flags.all)
+		},
+	}
+	DryccCmd.Flags().BoolVarP(&flags.all, "all", "a", false, i18n.T("list api and controller versions."))
+	return DryccCmd
+}
diff --git a/internal/parser/volumes.go b/internal/parser/volumes.go
new file mode 100644
index 000000000..18a5cd169
--- /dev/null
+++ b/internal/parser/volumes.go
@@ -0,0 +1,273 @@
+package parser
+
+import (
+	"github.com/drycc/workflow-cli/internal/commands"
+	"github.com/drycc/workflow-cli/internal/completion"
+	"github.com/drycc/workflow-cli/internal/template"
+	"github.com/drycc/workflow-cli/pkg/i18n"
+	"github.com/spf13/cobra"
+)
+
+// NewVolumesCommand creates the volumes command
+func NewVolumesCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "volumes",
+		Short: i18n.T("Manage volumes for your applications"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.VolumesList(app, results)
+		},
+	}
+
+	cmd.PersistentFlags().StringVarP(&app, "app", "a", "", i18n.T("The uniquely identifiable name for the application"))
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	appCompletion := completion.AppCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("app", appCompletion.CompletionFunc)
+
+	cmd.AddCommand(volumesListCommand(cmdr))
+	cmd.AddCommand(volumesAddCommand(cmdr))
+	cmd.AddCommand(volumesExpandCommand(cmdr))
+	cmd.AddCommand(volumesInfoCommand(cmdr))
+	cmd.AddCommand(volumesRemoveCommand(cmdr))
+	cmd.AddCommand(volumesClientCommand(cmdr))
+	cmd.AddCommand(volumesMountCommand(cmdr))
+	cmd.AddCommand(volumesUnmountCommand(cmdr))
+	return cmd
+}
+
+func volumesAddCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		vtype        string
+		name         string
+		size         string
+		nfsServer    string
+		nfsPath      string
+		ossServer    string
+		ossBucket    string
+		ossAccessKey string
+		ossSecretKey string
+	}
+
+	cmd := &cobra.Command{
+		Use: "add <name> <size>",
+		Example: template.CustomExample(
+			"drycc volumes add myvolume 1G",
+			map[string]string{
+				"<name>": i18n.T("The volume name"),
+				"<size>": i18n.T("The volume size, such as '500G'"),
+			},
+		),
+		Short: i18n.T("Create a volume for the application"),
+		Args:  cobra.ExactArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.name = args[0]
+			flags.size = args[1]
+			parameters := make(map[string]interface{})
+			if flags.vtype == "nfs" {
+				parameters["nfs"] = map[string]interface{}{
+					"server": flags.nfsServer,
+					"path":   flags.nfsPath,
+				}
+			} else if flags.vtype == "oss" {
+				parameters["oss"] = map[string]interface{}{
+					"server":     flags.ossServer,
+					"bucket":     flags.ossBucket,
+					"access_key": flags.ossAccessKey,
+					"secret_key": flags.ossSecretKey,
+				}
+			}
+			return cmdr.VolumesCreate(app, flags.name, flags.vtype, flags.size, parameters)
+		},
+	}
+
+	cmd.Flags().StringVarP(&flags.vtype, "type", "t", "csi", i18n.T("The volume type, such as csi, nfs, oss"))
+	cmd.Flags().StringVar(&flags.nfsServer, "nfs-server", "", i18n.T("The hostname or ip address of the nfs server"))
+	cmd.Flags().StringVar(&flags.nfsPath, "nfs-path", "", i18n.T("Path that is exported by the nfs server"))
+
+	cmd.Flags().StringVar(&flags.ossServer, "oss-server", "", i18n.T("Endpoint url for object storage service"))
+	cmd.Flags().StringVar(&flags.ossBucket, "oss-bucket", "", i18n.T("Bucket name in object storage"))
+	cmd.Flags().StringVar(&flags.ossAccessKey, "oss-access-key", "", i18n.T("Access key id for authentication"))
+	cmd.Flags().StringVar(&flags.ossSecretKey, "oss-secret-key", "", i18n.T("secret access key for authentication"))
+
+	cmd.Flags().SortFlags = false
+	cmd.MarkFlagsRequiredTogether("nfs-server", "nfs-path")
+	cmd.MarkFlagsRequiredTogether("oss-server", "oss-bucket", "oss-access-key", "oss-secret-key")
+
+	volumeTypeCompletion := completion.VolumeTypeCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile}
+	cmd.RegisterFlagCompletionFunc("type", volumeTypeCompletion.CompletionFunc)
+	return cmd
+}
+
+func volumesExpandCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name string
+		size string
+	}
+
+	volumeCompletion := completion.VolumeCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "expand <name> <size>",
+		Example: template.CustomExample(
+			"drycc volumes expand myvolume 2G",
+			map[string]string{
+				"<name>": i18n.T("The volume name"),
+				"<size>": i18n.T("The volume size, such as '500G'"),
+			},
+		),
+		Short:             i18n.T("Expand a volume capacity for the application"),
+		Args:              cobra.ExactArgs(2),
+		ValidArgsFunction: volumeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.name = args[0]
+			flags.size = args[1]
+			return cmdr.VolumesExpand(app, flags.name, flags.size)
+		},
+	}
+
+	return cmd
+}
+
+func volumesListCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: i18n.T("List volumes in the application"),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			results, _ := commands.ResponseLimit(limit)
+			return cmdr.VolumesList(app, results)
+		},
+	}
+
+	cmd.Flags().IntVarP(&limit, "limit", "l", 0, i18n.T("The maximum number of results to display"))
+
+	return cmd
+}
+
+func volumesInfoCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name string
+	}
+
+	volumeCompletion := completion.VolumeCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "info <name>",
+		Example: template.CustomExample(
+			"drycc volumes info myvolume",
+			map[string]string{
+				"<name>": i18n.T("The volume name to be info"),
+			},
+		),
+		Short:             i18n.T("Print information about a volume"),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: volumeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.name = args[0]
+			return cmdr.VolumesInfo(app, flags.name)
+		},
+	}
+
+	return cmd
+}
+
+func volumesRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name string
+	}
+
+	volumeCompletion := completion.VolumeCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "remove <name>",
+		Example: template.CustomExample(
+			"drycc volumes remove myvolume",
+			map[string]string{
+				"<name>": i18n.T("The volume name to be removed"),
+			},
+		),
+		Short:             i18n.T("Delete a volume from the application"),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: volumeCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			flags.name = args[0]
+			return cmdr.VolumesDelete(app, flags.name)
+		},
+	}
+
+	return cmd
+}
+
+func volumesClientCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	volumesCmdCompletion := completion.VolumesCmdCompletion{ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "client <cmd> <args>...",
+		Example: template.CustomExample(
+			"drycc volumes client ls vol://myvolume/",
+			map[string]string{
+				"<cmd>": i18n.T(`ls         list volume files
+           cp         copy volume files
+           rm         remove volume files`),
+				"<args>": i18n.T(`arguments for running commands, when cmd is 'cp', args should be '[source] [dest]'.
+           volume path format 'vol://volumename/', '/' is equivalent to the mount path.`),
+			},
+		),
+
+		Short:             i18n.T("The client used to manage volume files"),
+		Args:              cobra.MinimumNArgs(2),
+		ValidArgsFunction: volumesCmdCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cmdType := args[0]
+			cmdArgs := args[1:]
+			return cmdr.VolumesClient(app, cmdType, cmdArgs...)
+		},
+	}
+
+	return cmd
+}
+
+func volumesMountCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	volumesMountCompletion := completion.VolumesMountCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "mount <name> <ptype>=<path>...",
+		Example: template.CustomExample(
+			"drycc volumes mount myvolume web=/data",
+			map[string]string{
+				"<name>":  i18n.T("The volume name"),
+				"<ptype>": i18n.T("The process name as defined in your Procfile"),
+				"<path>":  i18n.T("The filesystem path"),
+			},
+		),
+		Short:             i18n.T("Mount a volume to process of the application"),
+		Args:              cobra.MinimumNArgs(2),
+		ValidArgsFunction: volumesMountCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			mountSpecs := args[1:]
+			return cmdr.VolumesMount(app, name, mountSpecs)
+		},
+	}
+
+	return cmd
+}
+
+func volumesUnmountCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	volumesUnmountCompletion := completion.VolumesUnmountCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use: "unmount <name> <ptype>...",
+		Example: template.CustomExample(
+			"drycc volumes unmount myvolume web worker",
+			map[string]string{
+				"<name>":  i18n.T("The volume name"),
+				"<ptype>": i18n.T("The process name as defined in your Procfile"),
+			},
+		),
+		Short:             i18n.T("Unmount a volume from process of the application"),
+		Args:              cobra.MinimumNArgs(2),
+		ValidArgsFunction: volumesUnmountCompletion.CompletionFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			name := args[0]
+			ptypes := args[1:]
+			return cmdr.VolumesUnmount(app, name, ptypes)
+		},
+	}
+
+	return cmd
+}
diff --git a/internal/template/template.go b/internal/template/template.go
new file mode 100644
index 000000000..8b99aca83
--- /dev/null
+++ b/internal/template/template.go
@@ -0,0 +1,42 @@
+package template
+
+import (
+	"bytes"
+	"strings"
+	"text/template"
+)
+
+func CustomExample(example string, kwargs map[string]string) string {
+	tpl, err := template.New("template").Funcs(template.FuncMap{
+		"rpad": func(str string, length int) string {
+			padding := length - len(str)
+			if padding > 0 {
+				return str + strings.Repeat(" ", padding)
+			}
+			return str
+		},
+	}).Parse(`{{.Example}}
+
+Args:
+  {{- range $key, $value := .Kwargs}}
+  {{rpad $key ($.Padding)}}{{$value}}
+  {{- end}}`)
+	if err != nil {
+		return example
+	}
+	padding, miniPadding := 0, 3
+	for key := range kwargs {
+		if padding < len(key)+miniPadding {
+			padding = len(key) + miniPadding
+		}
+	}
+	var buf bytes.Buffer
+	if err = tpl.Execute(&buf, struct {
+		Example string
+		Kwargs  map[string]string
+		Padding int
+	}{example, kwargs, padding}); err != nil {
+		return example
+	}
+	return buf.String()
+}
diff --git a/internal/utils/utils.go b/internal/utils/utils.go
new file mode 100644
index 000000000..d0a9b1c68
--- /dev/null
+++ b/internal/utils/utils.go
@@ -0,0 +1,25 @@
+package utils
+
+import (
+	"github.com/drycc/workflow-cli/pkg/git"
+	"github.com/drycc/workflow-cli/pkg/settings"
+)
+
+// LoadAppSettings loads settings file and looks up the app name
+func LoadAppSettings(cf string, appID string) (string, *settings.Settings, error) {
+	s, err := settings.Load(cf)
+
+	if err != nil {
+		return "", nil, err
+	}
+
+	if appID == "" {
+		appID, err = git.DetectAppName(git.DefaultCmd, s.Client.ControllerURL.Host)
+
+		if err != nil {
+			return "", nil, err
+		}
+	}
+
+	return appID, s, nil
+}
diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go
new file mode 100644
index 000000000..a77a4f025
--- /dev/null
+++ b/internal/utils/utils_test.go
@@ -0,0 +1,48 @@
+package utils
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	drycc "github.com/drycc/controller-sdk-go"
+	"github.com/drycc/workflow-cli/pkg/git"
+	"github.com/drycc/workflow-cli/pkg/settings"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLoadProject(t *testing.T) {
+	name, err := os.MkdirTemp("", "drycc-cli-unit-test-load")
+	assert.NoError(t, err)
+	defer os.RemoveAll(name)
+
+	filename := filepath.Join(name, "test.json")
+	host := "drycc.example.com"
+	client, err := drycc.New(false, host, "")
+	assert.NoError(t, err)
+
+	config := settings.Settings{
+		Username: "test",
+		Client:   client,
+	}
+
+	filename, err = config.Save(filename)
+	assert.NoError(t, err)
+
+	appID, _, err := LoadAppSettings(filename, "test")
+	assert.NoError(t, err)
+	assert.Equal(t, appID, "test", "app")
+
+	assert.NoError(t, os.Chdir(name))
+
+	appID, _, err = LoadAppSettings(filename, "")
+	assert.NoError(t, err)
+	assert.Equal(t, appID, filepath.Base(name), "app")
+
+	assert.NoError(t, git.Init(git.DefaultCmd))
+	assert.NoError(t, git.CreateRemote(git.DefaultCmd, host, "drycc", "testing"))
+
+	appID, _, err = LoadAppSettings(filename, "")
+	assert.NoError(t, err)
+	assert.Equal(t, appID, "testing", "app")
+}
diff --git a/parser/apps.go b/parser/apps.go
deleted file mode 100644
index 860750f9f..000000000
--- a/parser/apps.go
+++ /dev/null
@@ -1,287 +0,0 @@
-package parser
-
-import (
-	"strings"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Apps routes app commands to their specific function.
-func Apps(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for apps:
-
-apps:create        create a new application
-apps:list          list accessible applications
-apps:info          view info about an application
-apps:open          open the application in a browser
-apps:logs          view aggregated application logs
-apps:run           run a command in an ephemeral app container
-apps:destroy       destroy an application
-apps:transfer      transfer app ownership to another user
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "apps:create":
-		return appCreate(argv, cmdr)
-	case "apps:list":
-		return appsList(argv, cmdr)
-	case "apps:info":
-		return appInfo(argv, cmdr)
-	case "apps:open":
-		return appOpen(argv, cmdr)
-	case "apps:logs":
-		return appLogs(argv, cmdr)
-	case "apps:run":
-		return appRun(argv, cmdr)
-	case "apps:destroy":
-		return appDestroy(argv, cmdr)
-	case "apps:transfer":
-		return appTransfer(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "apps" {
-			argv[0] = "apps:list"
-			return appsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func appCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Creates a new application.
-
-- if no <id> is provided, one will be generated automatically.
-
-Usage: drycc apps:create [<id>] [options]
-
-Arguments:
-  <id>
-    a uniquely identifiable name for the application. No other app can already
-    exist with this name.
-
-Options:
-  --no-remote
-    do not create a 'drycc' git remote.
-  -r --remote REMOTE
-    name of remote to create. [default: drycc].
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	id := safeGetString(args, "<id>")
-	remote := safeGetString(args, "--remote")
-	noRemote := safeGetBool(args, "--no-remote")
-
-	return cmdr.AppCreate(id, remote, noRemote)
-}
-
-func appsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists applications visible to the current user.
-
-Usage: drycc apps:list [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AppsList(results)
-}
-
-func appInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about the current application.
-
-Usage: drycc apps:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.AppInfo(app)
-}
-
-func appOpen(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Opens a URL to the application in the default browser.
-
-Usage: drycc apps:open [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.AppOpen(app)
-}
-
-func appLogs(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Retrieves the most recent log events.
-
-Usage: drycc apps:logs [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -n --lines=<lines>
-    the number of lines to display.
-  -f --follow
-    specify if the logs should be streamed.
-  -t --timeout=<timeout>
-    the max seconds of follow the log stream.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	lines := safeGetInt(args, "--lines")
-	if lines <= 0 {
-		lines = 300
-	}
-	follow := safeGetBool(args, "--follow")
-	timeout := safeGetInt(args, "--timeout")
-	if timeout <= 0 {
-		timeout = 300
-	}
-
-	return cmdr.AppLogs(app, lines, follow, timeout)
-}
-
-func appRun(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Runs a command inside an ephemeral app container.
-
-Usage: drycc apps:run [--mount=<volume>:<path>...] [options] [--] <command>...
-
-Arguments:
-  <volume>
-    the volume name.
-  <path>
-    the filesystem path.
-  <command>
-    the shell command to run inside the container.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --timeout=<timeout>
-    the timeout for command run, default to 3600 seconds.
-  --expires=<expires>
-    retention time of running records, default to 3600 seconds.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	timeout := uint32(safeGetInt(args, "--timeout"))
-	expires := uint32(safeGetInt(args, "--expires"))
-	command := strings.Join(args["<command>"].([]string), " ")
-	mounts := args["--mount"].([]string)
-	return cmdr.AppRun(app, command, mounts, timeout, expires)
-}
-
-func appDestroy(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Destroys an application.
-
-Usage: drycc apps:destroy [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --confirm=<app>
-    skips the prompt for the application name. <app> is the uniquely identifiable
-    name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	confirm := safeGetString(args, "--confirm")
-
-	return cmdr.AppDestroy(app, confirm)
-}
-
-func appTransfer(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Transfer app ownership to another user.
-
-Usage: drycc apps:transfer <username> [options]
-
-Arguments:
-  <username>
-    the user that the app will be transferred to.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	user := safeGetString(args, "<username>")
-
-	return cmdr.AppTransfer(app, user)
-}
diff --git a/parser/apps_test.go b/parser/apps_test.go
deleted file mode 100644
index a378f80ce..000000000
--- a/parser/apps_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) AppCreate(string, string, bool) error {
-	return errors.New("apps:create")
-}
-
-func (d FakeDryccCmd) AppsList(int) error {
-	return errors.New("apps:list")
-}
-
-func (d FakeDryccCmd) AppInfo(string) error {
-	return errors.New("apps:info")
-}
-
-func (d FakeDryccCmd) AppOpen(string) error {
-	return errors.New("apps:open")
-}
-
-func (d FakeDryccCmd) AppLogs(string, int, bool, int) error {
-	return errors.New("apps:logs")
-}
-
-func (d FakeDryccCmd) AppRun(string, string, []string, uint32, uint32) error {
-	return errors.New("apps:run")
-}
-
-func (d FakeDryccCmd) AppDestroy(string, string) error {
-	return errors.New("apps:destroy")
-}
-
-func (d FakeDryccCmd) AppTransfer(string, string) error {
-	return errors.New("apps:transfer")
-}
-
-func TestApps(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"apps:create"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:open"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:logs"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:logs", "--lines=1"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:run", "ls"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:destroy"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps:transfer", "test-user"},
-			expected: "",
-		},
-		{
-			args:     []string{"apps"},
-			expected: "apps:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Apps(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/auth.go b/parser/auth.go
deleted file mode 100644
index 302e967eb..000000000
--- a/parser/auth.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package parser
-
-import (
-	"fmt"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Auth routes auth commands to the specific function.
-func Auth(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for auth:
-
-auth:login             authenticate against a controller
-auth:logout            clear the current user session
-auth:whoami            display the current user
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "auth:login":
-		return authLogin(argv, cmdr)
-	case "auth:logout":
-		return authLogout(argv, cmdr)
-	case "auth:whoami":
-		return authWhoami(argv, cmdr)
-	case "auth":
-		fmt.Print(usage)
-		return nil
-	default:
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func authLogin(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Logs in by authenticating against a controller.
-
-Usage: drycc auth:login <controller> [options]
-
-Arguments:
-  <controller>
-    a fully-qualified controller URI, e.g. "http://drycc.local3.dryccapp.com/".
-
-Options:
-  -u --username=<username>
-    provide a username for the account.
-  -p --password=<password>
-    provide a password for the account.
-  --ssl-verify=true
-    enables/disables SSL certificate verification for API requests.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	controller := safeGetString(args, "<controller>")
-	sslVerify := true
-	if args["--ssl-verify"] != nil && args["--ssl-verify"].(string) == "false" {
-		sslVerify = false
-	}
-	username := safeGetString(args, "--username")
-	password := safeGetString(args, "--password")
-
-	return cmdr.Login(controller, sslVerify, username, password)
-}
-
-func authLogout(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Logs out from a controller and clears the user session.
-
-Usage: drycc auth:logout
-`
-
-	if _, err := docopt.ParseArgs(usage, argv, ""); err != nil {
-		return err
-	}
-
-	return cmdr.Logout()
-}
-
-func authWhoami(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Displays the currently logged in user.
-
-Usage: drycc auth:whoami [options]
-
-Options:
-  --all
-    fetch a more detailed description about the user.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.Whoami(args["--all"].(bool))
-}
diff --git a/parser/auth_test.go b/parser/auth_test.go
deleted file mode 100644
index 333eae547..000000000
--- a/parser/auth_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) Login(string, bool, string, string) error {
-	return errors.New("auth:login")
-}
-
-func (d FakeDryccCmd) Logout() error {
-	return errors.New("auth:logout")
-}
-
-func (d FakeDryccCmd) Whoami(bool) error {
-	return errors.New("auth:whoami")
-}
-
-func TestAuth(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"auth:login", server.Server.URL},
-			expected: "",
-		},
-		{
-			args:     []string{"auth:logout"},
-			expected: "",
-		},
-		{
-			args:     []string{"auth:whoami"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Auth(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/autodeploy.go b/parser/autodeploy.go
deleted file mode 100644
index b9b86a7c1..000000000
--- a/parser/autodeploy.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Autodeploy displays all relevant commands for `drycc autodeploy`.
-func Autodeploy(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for autodeploy:
-
-autodeploy:info       view autodeploy info of an application
-autodeploy:enable     enable autodeploy for an app
-autodeploy:disable    disable autodeploy for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "autodeploy:info":
-		return autodeployInfo(argv, cmdr)
-	case "autodeploy:enable":
-		return autodeployEnable(argv, cmdr)
-	case "autodeploy:disable":
-		return autodeployDisable(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "autodeploy" {
-			argv[0] = "autodeploy:info"
-			return autodeployInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func autodeployInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about the current application's autodeploy if or not.
-
-Usage: drycc autodeploy:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutodeployInfo(safeGetString(args, "--app"))
-}
-
-func autodeployEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enables autodeploy for an app.
-
-Usage: drycc autodeploy:enable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutodeployEnable(safeGetString(args, "--app"))
-}
-
-func autodeployDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disables autodeploy for an app.
-
-Usage: drycc autodeploy:disable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutodeployDisable(safeGetString(args, "--app"))
-}
diff --git a/parser/autodeploy_test.go b/parser/autodeploy_test.go
deleted file mode 100644
index 717bf5ba7..000000000
--- a/parser/autodeploy_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) AutodeployInfo(string) error {
-	return errors.New("autodeploy:info")
-}
-
-func (d FakeDryccCmd) AutodeployEnable(string) error {
-	return errors.New("autodeploy:enable")
-}
-
-func (d FakeDryccCmd) AutodeployDisable(string) error {
-	return errors.New("autodeploy:disable")
-}
-
-func TestAutodeploy(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"autodeploy:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"autodeploy:enable"},
-			expected: "",
-		},
-		{
-			args:     []string{"autodeploy:disable"},
-			expected: "",
-		},
-		{
-			args:     []string{"autodeploy"},
-			expected: "autodeploy:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Autodeploy(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/autorollback.go b/parser/autorollback.go
deleted file mode 100644
index 0d6068308..000000000
--- a/parser/autorollback.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Autorollback displays all relevant commands for `drycc autorollback`.
-func Autorollback(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for autorollback:
-
-autorollback:info       view autorollback info of an application
-autorollback:enable     enable autorollback for an app
-autorollback:disable    disable autorollback for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "autorollback:info":
-		return autorollbackInfo(argv, cmdr)
-	case "autorollback:enable":
-		return autorollbackEnable(argv, cmdr)
-	case "autorollback:disable":
-		return autorollbackDisable(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "autorollback" {
-			argv[0] = "autorollback:info"
-			return autorollbackInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func autorollbackInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about the current application's autorollback if or not.
-
-Usage: drycc autorollback:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutorollbackInfo(safeGetString(args, "--app"))
-}
-
-func autorollbackEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enables autorollback for an app.
-
-Usage: drycc autorollback:enable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutorollbackEnable(safeGetString(args, "--app"))
-}
-
-func autorollbackDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disables autorollback for an app.
-
-Usage: drycc autorollback:disable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutorollbackDisable(safeGetString(args, "--app"))
-}
diff --git a/parser/autorollback_test.go b/parser/autorollback_test.go
deleted file mode 100644
index 80043cb83..000000000
--- a/parser/autorollback_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) AutorollbackInfo(string) error {
-	return errors.New("autorollback:info")
-}
-
-func (d FakeDryccCmd) AutorollbackEnable(string) error {
-	return errors.New("autorollback:enable")
-}
-
-func (d FakeDryccCmd) AutorollbackDisable(string) error {
-	return errors.New("autorollback:disable")
-}
-
-func TestAutorollback(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"autorollback:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"autorollback:enable"},
-			expected: "",
-		},
-		{
-			args:     []string{"autorollback:disable"},
-			expected: "",
-		},
-		{
-			args:     []string{"autorollback"},
-			expected: "autorollback:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Autorollback(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/autoscale.go b/parser/autoscale.go
deleted file mode 100644
index a0e734079..000000000
--- a/parser/autoscale.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Autoscale displays all relevant commands for `drycc autoscale`.
-func Autoscale(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for autoscale:
-
-autoscale:list   list autoscale options of an application
-autoscale:set    turn on autoscale for an app
-autoscale:unset  turn off autoscale for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "autoscale:list":
-		return autoscaleList(argv, cmdr)
-	case "autoscale:set":
-		return autoscaleSet(argv, cmdr)
-	case "autoscale:unset":
-		return autoscaleUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "autoscale" {
-			argv[0] = "autoscale:list"
-			return autoscaleList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func autoscaleList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints a list of autoscale options for the application.
-
-Usage: drycc autoscale:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.AutoscaleList(safeGetString(args, "--app"))
-}
-
-func autoscaleSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Set autoscale option per process type for an app.
-
-Usage: drycc autoscale:set <ptype> --min=<min> --max=<max> --cpu-percent=<percent> [options]
-
-Arguments:
-  <ptype>
-    the process type to add to the application's autoscale settings.
-  --min=<min>
-	minimum replicas to keep around.
-  --max=<max>
-	max replicas to scale up to.
-  --cpu-percent=<cpu-percent>
-	target CPU utilization.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	ptype := args["<ptype>"].(string)
-	app := safeGetString(args, "--app")
-	min := safeGetInt(args, "--min")
-	max := safeGetInt(args, "--max")
-	CPUPercent := safeGetInt(args, "--cpu-percent")
-
-	return cmdr.AutoscaleSet(app, ptype, min, max, CPUPercent)
-}
-
-func autoscaleUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unset autoscale per process type for an app.
-
-Usage: drycc autoscale:unset <ptype> [options]
-
-Arguments:
-  <ptype>
-    the process type to remove from the application's autoscale settings.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	ptype := args["<ptype>"].(string)
-	app := safeGetString(args, "--app")
-
-	return cmdr.AutoscaleUnset(app, ptype)
-}
diff --git a/parser/autoscale_test.go b/parser/autoscale_test.go
deleted file mode 100644
index a581b74af..000000000
--- a/parser/autoscale_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) AutoscaleList(string) error {
-	return errors.New("autoscale:list")
-}
-
-func (d FakeDryccCmd) AutoscaleSet(string, string, int, int, int) error {
-	return errors.New("autoscale:set")
-}
-
-func (d FakeDryccCmd) AutoscaleUnset(string, string) error {
-	return errors.New("autoscale:unset")
-}
-
-func TestAutoscale(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"autoscale:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"autoscale:set", "web", "--min=1", "--max=3", "--cpu-percent=50"},
-			expected: "",
-		},
-		{
-			args:     []string{"autoscale:unset", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"autoscale"},
-			expected: "autoscale:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Autoscale(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/builds.go b/parser/builds.go
deleted file mode 100644
index 56215b12e..000000000
--- a/parser/builds.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Builds routes build commands to their specific function.
-func Builds(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for builds:
-
-builds:info        print information about a specific build
-builds:create      imports an image and deploys as a new release
-builds:fetch       fetch the Procfile and dryccfile to the local
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "builds:info":
-		return buildsInfo(argv, cmdr)
-	case "builds:create":
-		return buildsCreate(argv, cmdr)
-	case "builds:fetch":
-		return buildsFetch(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "builds" {
-			argv[0] = "builds:info"
-			return buildsInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func buildsInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print information about a specific build.
-
-Usage: drycc builds:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -v --version=<version>
-    the version for which the build info needs to be displayed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-
-	return cmdr.BuildsInfo(safeGetString(args, "--app"), version)
-}
-
-func buildsCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Creates a new build of an application. Imports an <image> and deploys it to Drycc
-as a new release. If a Procfile or drycc.yaml is present in the current directory,
-it will be used as the default for this application.
-
-Usage: drycc builds:create <image> [options]
-
-Arguments:
-  <image>
-    A default fully-qualified container image, either from Drycc Registry (e.g. registry.drycc.cc/drycc/example-go:latest)
-    or from an in-house registry (e.g. myregistry.example.com:5000/example-go:latest).
-    This image must include the tag.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -s --stack=<stack>
-    the stack name for the application, defaults to container.
-  -p --procfile=<procfile>
-    a YAML file used to supply a Procfile to the application.
-  -d --dryccpath=<dryccpath>
-    drycc config path to the application, default is '.drycc'.
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	image := safeGetString(args, "<image>")
-	confirm := safeGetString(args, "--confirm")
-
-	stack := safeGetValue(args, "--stack", "container")
-	procfile := safeGetValue(args, "--procfile", "Procfile")
-	dryccpath := safeGetValue(args, "--dryccpath", ".drycc")
-
-	return cmdr.BuildsCreate(app, image, stack, procfile, dryccpath, confirm)
-}
-
-func buildsFetch(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print process info about a specific build.
-
-Usage: drycc builds:fetch [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -v --version=<version>
-    the version for which the build info needs to be fetched.
-  -p --procfile=<procfile>
-    the filename of the procfile saved locally, default is 'Procfile'.
-  -d --dryccpath=<dryccpath>
-    the folder name of the dryccfile saved locally, default is '.drycc'.
-  --confirm=yes
-    to proceed, type "yes".
-  --save
-    save process info to the local.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-
-	procfile := safeGetValue(args, "--procfile", "Procfile")
-	dryccpath := safeGetValue(args, "--dryccpath", ".drycc")
-	save := args["--save"].(bool)
-	confirm := safeGetString(args, "--confirm")
-
-	return cmdr.BuildsFetch(app, version, procfile, dryccpath, confirm, save)
-}
diff --git a/parser/builds_test.go b/parser/builds_test.go
deleted file mode 100644
index 419870064..000000000
--- a/parser/builds_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-func (d FakeDryccCmd) BuildsInfo(string, int) error {
-	return errors.New("builds:info")
-}
-
-func (d FakeDryccCmd) BuildsCreate(string, string, string, string, string, string) error {
-	return errors.New("builds:create")
-}
-
-func (d FakeDryccCmd) BuildsFetch(string, int, string, string, string, bool) error {
-	return errors.New("builds:fetch")
-}
-
-func TestBuilds(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"builds:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"builds:create", "drycc/example-go:latest"},
-			expected: "",
-		},
-		{
-			args:     []string{"builds:fetch"},
-			expected: "",
-		},
-		{
-			args:     []string{"builds"},
-			expected: "builds:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Builds(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/certs.go b/parser/certs.go
deleted file mode 100644
index c8455dc65..000000000
--- a/parser/certs.go
+++ /dev/null
@@ -1,208 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Certs routes certs commands to their specific function.
-func Certs(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for certs:
-
-certs:list            list SSL certificates for an app
-certs:add             add an SSL certificate to an app
-certs:remove          remove an SSL certificate from an app
-certs:info            get detailed informaton about the certificate
-certs:attach          attach an SSL certificate to a domain
-certs:detach          detach an SSL certificate from a domain
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "certs:list":
-		return certsList(argv, cmdr)
-	case "certs:add":
-		return certAdd(argv, cmdr)
-	case "certs:remove":
-		return certRemove(argv, cmdr)
-	case "certs:info":
-		return certInfo(argv, cmdr)
-	case "certs:attach":
-		return certAttach(argv, cmdr)
-	case "certs:detach":
-		return certDetach(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "certs" {
-			argv[0] = "certs:list"
-			return certsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func certsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Show certificate information for an SSL application.
-
-Usage: drycc certs:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	return cmdr.CertsList(app, results)
-}
-
-func certAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Binds a certificate/key pair to an application.
-
-Usage: drycc certs:add <name> <cert> <key> [options]
-
-Arguments:
-  <name>
-    Name of the certificate to reference it by.
-  <cert>
-    The public key of the SSL certificate.
-  <key>
-    The private key of the SSL certificate.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	name := args["<name>"].(string)
-	cert := args["<cert>"].(string)
-	key := args["<key>"].(string)
-	app := safeGetString(args, "--app")
-	return cmdr.CertAdd(app, cert, key, name)
-}
-
-func certRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Remove a certificate/key pair from the application.
-
-Usage: drycc certs:remove <name> [options]
-
-Arguments:
-  <name>
-    the name of the cert to remove from the app.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	return cmdr.CertRemove(app, name)
-}
-
-func certInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Fetch more detailed information about a certificate.
-
-Usage: drycc certs:info <name> [options]
-
-Arguments:
-  <name>
-    the name of the cert to get information from.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	return cmdr.CertInfo(app, name)
-}
-
-func certAttach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Attach a certificate to a domain.
-
-Usage: drycc certs:attach <name> <domain> [options]
-
-Arguments:
-  <name>
-    name of the certificate to attach domain to.
-  <domain>
-    common name of the domain to attach to (needs to already be in the system).
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	domain := safeGetString(args, "<domain>")
-	return cmdr.CertAttach(app, name, domain)
-}
-
-func certDetach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Detach a certificate from a domain.
-
-Usage: drycc certs:detach <name> <domain> [options]
-
-Arguments:
-  <name>
-    name of the certificate to deatch from a domain
-  <domain>
-    common name of the domain to detach from
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	domain := safeGetString(args, "<domain>")
-	return cmdr.CertDetach(app, name, domain)
-}
diff --git a/parser/certs_test.go b/parser/certs_test.go
deleted file mode 100644
index 7fa5fce74..000000000
--- a/parser/certs_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) CertsList(string, int) error {
-	return errors.New("certs:list")
-}
-
-func (d FakeDryccCmd) CertAdd(string, string, string, string) error {
-	return errors.New("certs:add")
-}
-
-func (d FakeDryccCmd) CertRemove(string, string) error {
-	return errors.New("certs:remove")
-}
-
-func (d FakeDryccCmd) CertInfo(string, string) error {
-	return errors.New("certs:info")
-}
-
-func (d FakeDryccCmd) CertAttach(string, string, string) error {
-	return errors.New("certs:attach")
-}
-
-func (d FakeDryccCmd) CertDetach(string, string, string) error {
-	return errors.New("certs:detach")
-}
-
-func TestCerts(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"certs:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs:add", "name", "cert", "key"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs:remove", "name"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs:info", "name"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs:attach", "name", "example.com"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs:detach", "name", "example.com"},
-			expected: "",
-		},
-		{
-			args:     []string{"certs"},
-			expected: "certs:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Certs(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/config.go b/parser/config.go
deleted file mode 100644
index 5e6d77e84..000000000
--- a/parser/config.go
+++ /dev/null
@@ -1,292 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Config routes config commands to their specific function.
-func Config(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for config:
-
-config:info        an app config info
-config:set         set environment variables for an app
-config:unset       unset environment variables for an app
-config:pull        pull environment variables to the path
-config:push        push environment variables from the path
-config:attach      selects a environment groups to attach an app ptype
-config:detach      selects a environment groups to detach an app ptype
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "config:info":
-		return configInfo(argv, cmdr)
-	case "config:set":
-		return configSet(argv, cmdr)
-	case "config:unset":
-		return configUnset(argv, cmdr)
-	case "config:pull":
-		return configPull(argv, cmdr)
-	case "config:push":
-		return configPush(argv, cmdr)
-	case "config:attach":
-		return configAttach(argv, cmdr)
-	case "config:detach":
-		return configDetach(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "config" {
-			argv[0] = "config:info"
-			return configInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func configInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-An app config info.
-
-Usage: drycc config:info [options]
-
-Options:
-  -a --app=<app>
-    the application that you wish to listed.
-  -p --ptype=<ptype>
-    the ptype for which the config needs to be listed.
-  -g --group=<group>
-    the group for which the config needs to be listed.
-  -v --version=<version>
-    the version for which the config needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	group := safeGetString(args, "--group")
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-
-	return cmdr.ConfigInfo(app, ptype, group, version)
-}
-
-func configSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets environment variables for an application or config group.
-
-Usage: drycc config:set <var>=<value> [<var>=<value>...] [options]
-
-Arguments:
-  <var>
-    the uniquely identifiable name for the environment variable.
-  <value>
-    the value of said environment variable.
-
-Options:
-  -a --app=<app>
-    the application that you wish to set.
-  -p --ptype=<ptype>
-    the ptype for which the config needs to be set.
-  -g --group=<group>
-    the group for which the config needs to be set.
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	group := safeGetString(args, "--group")
-	confirm := safeGetString(args, "--confirm")
-	return cmdr.ConfigSet(app, ptype, group, args["<var>=<value>"].([]string), confirm)
-}
-
-func configUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets an environment variable for an application or config group.
-
-Usage: drycc config:unset <key>... [options]
-
-Arguments:
-  <key>
-    the variable to remove from the application's environment.
-
-Options:
-  -a --app=<app>
-    the application that you wish to unset.
-  -p --ptype=<ptype>
-    the ptype for which the config needs to be unset.
-  -g --group=<group>
-    the group for which the config needs to be unset.
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	group := safeGetString(args, "--group")
-	confirm := safeGetString(args, "--confirm")
-	return cmdr.ConfigUnset(app, ptype, group, args["<key>"].([]string), confirm)
-}
-
-func configPull(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Extract all environment variables from an application or config group. for local use.
-
-The environmental variables can be piped into a file, 'drycc config:pull > file',
-or stored locally in a file named .env. This file can be
-read by foreman to load the local environment for your app.
-
-Usage: drycc config:pull [options]
-
-Options:
-  -a --app=<app>
-    the application that you wish to pull.
-  -p --ptype=<ptype>
-    the ptype for which the config needs to be pull.
-  -g --group=<group>
-    the group for which the config needs to be push.
-  --path=<path>
-    a path leading to an environment file [default: .env]
-  -i --interactive
-    prompts for each value to be overwritten.
-  -o --overwrite
-    allows you to have the pull overwrite keys to the path.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	group := safeGetString(args, "--group")
-	path := safeGetValue(args, "--path", ".env")
-	interactive := args["--interactive"].(bool)
-	overwrite := args["--overwrite"].(bool)
-
-	return cmdr.ConfigPull(app, ptype, group, path, interactive, overwrite)
-}
-
-func configPush(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets environment variables for an application or config group.
-
-This file can be read by foreman
-to load the local environment for your app. The file should be piped via
-stdin, 'drycc config:push < .env', or using the --path option.
-
-Usage: drycc config:push [options]
-
-Options:
-  -a --app=<app>
-    the application that you wish to push.
-  -p --ptype=<ptype>
-    the ptype for which the config needs to be push.
-  -g --group=<group>
-    the group for which the config needs to be push.
-  --path=<path>
-    a path leading to an environment file [default: .env]
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	group := safeGetString(args, "--group")
-	path := safeGetValue(args, "--path", ".env")
-	confirm := safeGetString(args, "--confirm")
-	return cmdr.ConfigPush(app, ptype, group, path, confirm)
-}
-
-func configAttach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Selects a environment groups to attach an app ptype.
-
-Usage: drycc config:attach <ptype> <groups> [options]
-
-Arguments:
-  <ptype>
-    the ptype that requires attach configurations.
-  <groups>
-    comma separated list of config groups.
-
-Options:
-  -a --app=<app>
-    the application that you wish to attach.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "<ptype>")
-	groups := safeGetString(args, "<groups>")
-	return cmdr.ConfigAttach(app, ptype, groups)
-}
-
-func configDetach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Selects a environment groups to detach an app ptype.
-
-Usage: drycc config:detach <ptype> <groups> [options]
-
-Arguments:
-  <ptype>
-    the ptype that requires detach configurations.
-  <groups>
-    comma separated list of config groups.
-
-Options:
-  -a --app=<app>
-    the application that you wish to detach.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "<ptype>")
-	groups := safeGetString(args, "<groups>")
-	return cmdr.ConfigDetach(app, ptype, groups)
-}
diff --git a/parser/config_test.go b/parser/config_test.go
deleted file mode 100644
index f38eab583..000000000
--- a/parser/config_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) ConfigInfo(string, string, string, int) error {
-	return errors.New("config:info")
-}
-
-func (d FakeDryccCmd) ConfigSet(string, string, string, []string, string) error {
-	return errors.New("config:set")
-}
-
-func (d FakeDryccCmd) ConfigUnset(string, string, string, []string, string) error {
-	return errors.New("config:unset")
-}
-
-func (d FakeDryccCmd) ConfigPull(string, string, string, string, bool, bool) error {
-	return errors.New("config:pull")
-}
-
-func (d FakeDryccCmd) ConfigPush(string, string, string, string, string) error {
-	return errors.New("config:push")
-}
-
-func (d FakeDryccCmd) ConfigAttach(string, string, string) error {
-	return errors.New("config:attach")
-}
-
-func (d FakeDryccCmd) ConfigDetach(string, string, string) error {
-	return errors.New("config:detach")
-}
-
-func TestConfig(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"config:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:set", "var=value"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:unset", "var"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:pull"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:push"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:attach", "web", "g1"},
-			expected: "",
-		},
-		{
-			args:     []string{"config:detach", "web", "g1"},
-			expected: "",
-		},
-		{
-			args:     []string{"config"},
-			expected: "config:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Config(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/domains.go b/parser/domains.go
deleted file mode 100644
index e9594a9b2..000000000
--- a/parser/domains.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Domains routes domain commands to their specific function.
-func Domains(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for domains:
-
-domains:add           bind a domain to an application
-domains:list          list domains bound to an application
-domains:remove        unbind a domain from an application
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "domains:add":
-		return domainsAdd(argv, cmdr)
-	case "domains:list":
-		return domainsList(argv, cmdr)
-	case "domains:remove":
-		return domainsRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "domains" {
-			argv[0] = "domains:list"
-			return domainsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func domainsAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Binds a domain to an application.
-
-Usage: drycc domains:add <domain> [options]
-
-Arguments:
-  <domain>
-    the domain name to be bound to the application, such as 'domain.dryccapp.com'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptype=<ptype>
-    the ptype for domain, default[web].
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetValue(args, "--ptype", "web")
-	domain := safeGetString(args, "<domain>")
-
-	return cmdr.DomainsAdd(app, domain, ptype)
-}
-
-func domainsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists domains bound to an application.
-
-Usage: drycc domains:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-
-	return cmdr.DomainsList(app, results)
-}
-
-func domainsRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unbinds a domain for an application.
-
-Usage: drycc domains:remove <domain> [options]
-
-Arguments:
-  <domain>
-    the domain name to be removed from the application.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	domain := safeGetString(args, "<domain>")
-
-	return cmdr.DomainsRemove(app, domain)
-}
diff --git a/parser/domains_test.go b/parser/domains_test.go
deleted file mode 100644
index d82da0cfb..000000000
--- a/parser/domains_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) DomainsList(string, int) error {
-	return errors.New("domains:list")
-}
-
-func (d FakeDryccCmd) DomainsAdd(string, string, string) error {
-	return errors.New("domains:add")
-}
-
-func (d FakeDryccCmd) DomainsRemove(string, string) error {
-	return errors.New("domains:remove")
-}
-
-func TestDomains(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"domains:add", "example.com"},
-			expected: "",
-		},
-		{
-			args:     []string{"domains:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"domains:remove", "example.com"},
-			expected: "",
-		},
-		{
-			args:     []string{"domains"},
-			expected: "domains:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Domains(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/gateways.go b/parser/gateways.go
deleted file mode 100644
index 6b6649573..000000000
--- a/parser/gateways.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Gateways gateways commands to their specific function.
-func Gateways(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for gateways:
-
-gateways:add           create gateways for an application
-gateways:list          list application gateways
-gateways:remove        remove gateways from an application
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "gateways:add":
-		return gatewaysAdd(argv, cmdr)
-	case "gateways:list":
-		return gatewaysList(argv, cmdr)
-	case "gateways:remove":
-		return gatewaysRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "gateways" {
-			argv[0] = "gateways:list"
-			return gatewaysList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func gatewaysAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Creates gateways for an application and binds it to allow listener of the main app domain.
-
-Usage: drycc gateways:add <name> --port=<port> --protocol=<protocol> [options]
-
-Arguments:
-  <name>
-    the gateway name.
-  <port> 
-    port is the network port, the listener expects to receive.
-  <protocol>
-    protocol specifies the network protocol this listener expects to receive. Supports TCP, UDP, TLS, HTTP, and HTTPS.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	port := safeGetInt(args, "--port")
-	protocol := safeGetString(args, "--protocol")
-
-	return cmdr.GatewaysAdd(app, name, port, protocol)
-}
-
-func gatewaysList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists gateways for an application.
-
-Usage: drycc gateways:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	results, err := responseLimit(safeGetString(args, "--limit"))
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.GatewaysList(app, results)
-}
-
-func gatewaysRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Deletes specific gateway for application.
-
-Usage: drycc gateways:remove <name> --port=<port> --protocol=<protocol> [options]
-
-Arguments:
-  <name>
-    the gateway name.
-  <port> 
-    port is the network port, the listener expects to receive.
-  <protocol>
-    protocol specifies the network protocol this listener expects to receive.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	port := safeGetInt(args, "--port")
-	protocol := safeGetString(args, "--protocol")
-
-	return cmdr.GatewaysRemove(app, name, port, protocol)
-}
diff --git a/parser/gateways_test.go b/parser/gateways_test.go
deleted file mode 100644
index f22733f04..000000000
--- a/parser/gateways_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) GatewaysList(string, int) error {
-	return errors.New("gateways:list")
-}
-
-func (d FakeDryccCmd) GatewaysAdd(string, string, int, string) error {
-	return errors.New("gateways:add")
-}
-
-func (d FakeDryccCmd) GatewaysRemove(string, string, int, string) error {
-	return errors.New("gateways:remove")
-}
-
-func TestGateways(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"gateways:add", "example", "--port=80", "--protocol=http"},
-			expected: "",
-		},
-		{
-			args:     []string{"gateways:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"gateways:remove", "example", "--port=80", "--protocol=http"},
-			expected: "",
-		},
-		{
-			args:     []string{"gateways"},
-			expected: "gateways:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Gateways(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/git.go b/parser/git.go
deleted file mode 100644
index 2212dab5f..000000000
--- a/parser/git.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package parser
-
-import (
-	"fmt"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Git routes git commands to their specific function.
-func Git(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for git:
-
-git:remote          Adds git remote of application to repository
-git:remove          Removes git remote of application from repository
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "git:remote":
-		return gitRemote(argv, cmdr)
-	case "git:remove":
-		return gitRemove(argv, cmdr)
-	case "git":
-		fmt.Print(usage)
-		return nil
-	default:
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func gitRemote(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Adds git remote of application to repository.
-
-Usage: drycc git:remote [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -r --remote=REMOTE
-    name of remote to create. [default: drycc]
-  -f --force
-    overwrite remote of the given name if it already exists.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	remote := safeGetString(args, "--remote")
-	force := args["--force"].(bool)
-
-	return cmdr.GitRemote(app, remote, force)
-}
-
-func gitRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Removes git remotes of application from repository.
-
-Usage: drycc git:remove [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.GitRemove(safeGetString(args, "--app"))
-}
diff --git a/parser/git_test.go b/parser/git_test.go
deleted file mode 100644
index a42a25076..000000000
--- a/parser/git_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) GitRemote(string, string, bool) error {
-	return errors.New("git:remote")
-}
-
-func (d FakeDryccCmd) GitRemove(string) error {
-	return errors.New("git:remove")
-}
-
-func TestGit(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"git:remote"},
-			expected: "",
-		},
-		{
-			args:     []string{"git:remove"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Git(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/healthchecks.go b/parser/healthchecks.go
deleted file mode 100644
index 1013ef722..000000000
--- a/parser/healthchecks.go
+++ /dev/null
@@ -1,302 +0,0 @@
-package parser
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/drycc/workflow-cli/cmd"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/controller-sdk-go/api"
-)
-
-// TODO: This is for supporting backward compatibility and should be removed
-// in future when next major version will be released.
-const (
-	defaultPtype string = "web"
-)
-
-// Healthchecks routes ealthcheck commands to their specific function
-func Healthchecks(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for healthchecks:
-
-healthchecks:list        list healthchecks for an app
-healthchecks:set         set healthchecks for an app
-healthchecks:unset       unset healthchecks for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "healthchecks:list":
-		return healthchecksList(argv, cmdr)
-	case "healthchecks:set":
-		return healthchecksSet(argv, cmdr)
-	case "healthchecks:unset":
-		return healthchecksUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "healthchecks" {
-			argv[0] = "healthchecks:list"
-			return healthchecksList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func healthchecksList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists healthchecks for an application.
-
-Usage: drycc healthchecks:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-  -p --ptype=<ptype>
-    the ptype for which the health check needs to be listed.
-  -v --version=<version>
-    the version for which the health check needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "--ptype")
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-	return cmdr.HealthchecksList(app, ptype, version)
-}
-
-func healthchecksSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets healthchecks for an application.
-
-By default, Workflow only checks that the application starts in their Container. A health
-check may be added by configuring a health check probe for the application. The health
-checks are implemented as Kubernetes Container Probes. A 'startupProbe' 'livenessProbe' 
-and a 'readinessProbe' can be configured, and each probe can be of type 'httpGet', 'exec' 
-or 'tcpSocket' depending on the type of probe the Container requires.
-
-A 'startupProbe' indicates whether the application within the container is started.
-All other probes are disabled if a startup probe is provided, until it succeeds.
-If the startup probe fails, the container is subjected to its restart policy.
-
-A 'livenessProbe' is useful for applications running for long periods of time, eventually
-transitioning to broken states and cannot recover except by restarting them.
-
-Other times, a 'readinessProbe' is useful when the Container is only temporarily unable
-to serve, and will recover on its own. In this case, if a Container fails its 'readinessProbe'
-, the Container will not be shut down, but rather the Container will stop receiving
-incoming requests.
-
-'httpGet' probes are just as it sounds: it performs a HTTP GET operation on the Container.
-A response code inside the 200-399 range is considered a pass. 'httpGet' probes accept a
-port number to perform the HTTP GET operation on the Container.
-
-'exec' probes run a command inside the Container to determine its health. An exit code of
-zero is considered a pass, while a non-zero status code is considered a fail. 'exec'
-probes accept a string of arguments to be run inside the Container.
-
-'tcpSocket' probes attempt to open a socket in the Container. The Container is only
-considered healthy if the check can establish a connection. 'tcpSocket' probes accept a
-port number to perform the socket connection on the Container.
-
-Usage: drycc healthchecks:set <health-type> <probe-type> [options] [--] <args>...
-
-Arguments:
-  <health-type>
-    the healthcheck type, such as 'startupProbe' 'livenessProbe' or 'readinessProbe'.
-  <probe-type>
-    the healthcheck probe type, such as 'httpGet', 'exec' or 'tcpSocket'.
-  <args>
-    The arguments required for the healthcheck probe. 'exec', accepts a list of arguments;
-    'httpGet' and 'tcpSocket' accept a port number.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptype=<ptype>
-    the ptype for which the health check needs to be applied.
-  --path=<path>
-    the relative URL path for 'httpGet' probes. [default: /]
-  --headers=<headers>...
-    the HTTP headers to send for 'httpGet' probes, separated by commas.
-  --initial-delay-timeout=<initial-delay-timeout>
-    the initial delay timeout for the probe [default: 50].
-  --timeout-seconds=<timeout-seconds>
-    the number of seconds after which the probe times out [default: 50]
-  --period-seconds=<period-seconds>
-    how often (in seconds) to perform the probe [default: 10].
-  --success-threshold=<success-threshold>
-    minimum consecutive successes for the probe to be considered successful after having failed [default: 1].
-  --failure-threshold=<failure-threshold>
-    minimum consecutive successes for the probe to be considered failed after having succeeded [default: 3].
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	path := safeGetString(args, "--path")
-	ptype := safeGetString(args, "--ptype")
-	initialDelayTimeout := safeGetInt(args, "--initial-delay-timeout")
-	timeoutSeconds := safeGetInt(args, "--timeout-seconds")
-	periodSeconds := safeGetInt(args, "--period-seconds")
-	successThreshold := safeGetInt(args, "--success-threshold")
-	failureThreshold := safeGetInt(args, "--failure-threshold")
-	headers := []string{}
-	if args["--headers"] != nil {
-		headers = strings.Split(args["--headers"].(string), ",")
-	}
-	if ptype == "" {
-		ptype = defaultPtype
-	}
-
-	healthcheckType := args["<health-type>"].(string)
-	probeType := args["<probe-type>"].(string)
-	probeArgs := args["<args>"].([]string)
-
-	if err := checkProbeType(healthcheckType); err != nil {
-		return err
-	}
-
-	probe := &api.Healthcheck{
-		InitialDelaySeconds: initialDelayTimeout,
-		TimeoutSeconds:      timeoutSeconds,
-		PeriodSeconds:       periodSeconds,
-		SuccessThreshold:    successThreshold,
-		FailureThreshold:    failureThreshold,
-	}
-
-	switch probeType {
-	case "httpGet":
-		parsedHeaders, err := parseHeaders(headers)
-		if err != nil {
-			return fmt.Errorf("could not parse headers: %s", err)
-		}
-		port, err := strconv.Atoi(probeArgs[0])
-		if err != nil {
-			return fmt.Errorf("could not parse port: %s", err)
-		}
-		probe.HTTPGet = &api.HTTPGetProbe{
-			Path:        path,
-			Port:        port,
-			HTTPHeaders: parsedHeaders,
-		}
-	case "exec":
-		probe.Exec = &api.ExecProbe{
-			Command: probeArgs,
-		}
-	case "tcpSocket":
-		port, err := strconv.Atoi(probeArgs[0])
-		if err != nil {
-			return fmt.Errorf("could not parse port: %s", err)
-		}
-		probe.TCPSocket = &api.TCPSocketProbe{
-			Port: port,
-		}
-	default:
-		return fmt.Errorf("invalid probe type. Must be one of: \"httpGet\", \"exec\"")
-	}
-
-	return cmdr.HealthchecksSet(app, healthcheckType, ptype, probe)
-}
-
-func healthchecksUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets healthchecks for an application.
-
-Usage: drycc healthchecks:unset <health-type>... [options]
-
-Arguments:
-  <health-type>
-    the healthcheck type, such as 'startupProbe' 'livenessProbe' or 'readinessProbe'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptype=<ptype>
-    the ptype for which the health check needs to be removed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	healthchecks := args["<health-type>"].([]string)
-	ptype := safeGetString(args, "--ptype")
-	if ptype == "" {
-		ptype = defaultPtype
-	}
-
-	for healthcheck := range healthchecks {
-		if err := checkProbeType(healthchecks[healthcheck]); err != nil {
-			return err
-		}
-	}
-
-	return cmdr.HealthchecksUnset(app, ptype, healthchecks)
-}
-
-func parseHeaders(headers []string) ([]*api.KVPair, error) {
-	var parsedHeaders []*api.KVPair
-	for _, header := range headers {
-		parsedHeader, err := parseHeader(header)
-		if err != nil {
-			return nil, err
-		}
-		parsedHeaders = append(parsedHeaders, parsedHeader)
-	}
-	return parsedHeaders, nil
-}
-
-func parseHeader(header string) (*api.KVPair, error) {
-	headerParts := strings.SplitN(header, ":", 2)
-	if len(headerParts) != 2 {
-		return nil, fmt.Errorf("could not find separator in header (%s)", header)
-	}
-	return &api.KVPair{
-		Name:  strings.TrimSpace(headerParts[0]),
-		Value: strings.TrimSpace(headerParts[1]),
-	}, nil
-}
-
-func checkProbeType(probe string) error {
-	var found bool
-	probeTypes := []string{
-		"startupProbe",
-		"livenessProbe",
-		"readinessProbe",
-	}
-	for _, probeType := range probeTypes {
-		if probe == probeType {
-			found = true
-		}
-	}
-	if !found {
-		return fmt.Errorf("probe type %s is invalid. Must be one of %s", probe, probeTypes)
-	}
-	return nil
-}
diff --git a/parser/healthchecks_test.go b/parser/healthchecks_test.go
deleted file mode 100644
index 19d387ebd..000000000
--- a/parser/healthchecks_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/controller-sdk-go/api"
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) HealthchecksList(string, string, int) error {
-	return errors.New("healthchecks:list")
-}
-
-func (d FakeDryccCmd) HealthchecksSet(string, string, string, *api.Healthcheck) error {
-	return errors.New("healthchecks:set")
-}
-
-func (d FakeDryccCmd) HealthchecksUnset(string, string, []string) error {
-	return errors.New("healthchecks:unset")
-}
-
-func TestHealthchecks(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"healthchecks:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks:set", "liveness", "httpGet", "80"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks:set", "liveness", "httpGet", "80", "--headers=test-header:test-value"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks:set", "liveness", "exec", "ls"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks:set", "liveness", "tcpSocket", "80"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks:unset", "liveness"},
-			expected: "",
-		},
-		{
-			args:     []string{"healthchecks"},
-			expected: "healthchecks:list",
-		},
-		{
-			args:     []string{"healthchecks:set", "alien", "httpGet", "80"},
-			expected: "probe type alien is invalid. Must be one of [liveness readiness]",
-		},
-		{
-			args:     []string{"healthchecks:unset", "alien", "httpGet", "80"},
-			expected: "probe type alien is invalid. Must be one of [liveness readiness]",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Healthchecks(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/keys.go b/parser/keys.go
deleted file mode 100644
index b5f34c5f4..000000000
--- a/parser/keys.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Keys routes key commands to the specific function.
-func Keys(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for SSH keys:
-
-keys:list        list SSH keys for the logged in user
-keys:add         add an SSH key
-keys:remove      remove an SSH key
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "keys:list":
-		return keysList(argv, cmdr)
-	case "keys:add":
-		return keyAdd(argv, cmdr)
-	case "keys:remove":
-		return keyRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "keys" {
-			argv[0] = "keys:list"
-			return keysList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func keysList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists SSH keys for the logged in user.
-
-Usage: drycc keys:list [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.KeysList(results)
-}
-
-func keyAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Adds SSH keys for the logged in user.
-
-Usage: drycc keys:add [<name>] [<key>]
-
-<name> and <key> can be used in either order and are both optional.
-
-Arguments:
-  <name>
-    name of the SSH key.
-  <key>
-    a local file path to an SSH public key used to push application code.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.KeyAdd(safeGetString(args, "<name>"), safeGetString(args, "<key>"))
-}
-
-func keyRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Removes an SSH key for the logged in user.
-
-Usage: drycc keys:remove <key>
-
-Arguments:
-  <key>
-    the SSH public key to revoke source code push access.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.KeyRemove(safeGetString(args, "<key>"))
-}
diff --git a/parser/keys_test.go b/parser/keys_test.go
deleted file mode 100644
index 02f7679e8..000000000
--- a/parser/keys_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) KeysList(int) error {
-	return errors.New("keys:list")
-}
-
-func (d FakeDryccCmd) KeyRemove(string) error {
-	return errors.New("keys:remove")
-}
-
-func (d FakeDryccCmd) KeyAdd(string, string) error {
-	return errors.New("keys:add")
-}
-
-func TestKeys(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"keys:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"keys:add", "name", "key"},
-			expected: "",
-		},
-		{
-			args:     []string{"keys:remove", "key"},
-			expected: "",
-		},
-		{
-			args:     []string{"keys"},
-			expected: "keys:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Keys(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/labels.go b/parser/labels.go
deleted file mode 100644
index d454aaece..000000000
--- a/parser/labels.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Labels displays all relevant commands for `drycc label`.
-func Labels(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for labels:
-
-labels:list   list application's labels
-labels:set    add new application's label
-labels:unset  remove application's label
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "labels:list":
-		return labelsList(argv, cmdr)
-	case "labels:set":
-		return labelsSet(argv, cmdr)
-	case "labels:unset":
-		return labelsUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "labels" {
-			argv[0] = "labels:list"
-			return labelsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func labelsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints a list of labels of the application.
-
-Usage: drycc labels:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.LabelsList(safeGetString(args, "--app"))
-}
-
-func labelsSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets labels for an application.
-
-A label is a key/value pair used to label an application. This label is a general information for drycc user.
-Mostly used for administration/maintenance information, note for application. This information isn't send to scheduler.
-
-Usage: drycc labels:set <key>=<value>... [options]
-
-Arguments:
-  <key> the label key, for example: "git_repo" or "team"
-  <value> the label value, for example: "https://github.com/drycc/workflow" or "frontend"
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	tags := args["<key>=<value>"].([]string)
-
-	return cmdr.LabelsSet(app, tags)
-}
-
-func labelsUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets labels for an application.
-
-Usage: drycc labels:unset <key>... [options]
-
-Arguments:
-  <key> the label key to unset, for example: "git_repo" or "team".
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	tags := args["<key>"].([]string)
-
-	return cmdr.LabelsUnset(app, tags)
-}
diff --git a/parser/labels_test.go b/parser/labels_test.go
deleted file mode 100644
index 0b75f93b2..000000000
--- a/parser/labels_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) LabelsList(string) error {
-	return errors.New("labels:list")
-}
-
-func (d FakeDryccCmd) LabelsSet(string, []string) error {
-	return errors.New("labels:set")
-}
-
-func (d FakeDryccCmd) LabelsUnset(string, []string) error {
-	return errors.New("labels:unset")
-}
-
-func TestLabels(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"labels:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"labels:set", "git_repo=https://github.com/drycc/workflow", "team=drycc"},
-			expected: "",
-		},
-		{
-			args:     []string{"labels:unset", "git_repo", "team"},
-			expected: "",
-		},
-		{
-			args:     []string{"labels"},
-			expected: "labels:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Labels(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/limits.go b/parser/limits.go
deleted file mode 100644
index 3f013d977..000000000
--- a/parser/limits.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Limits routes limits commands to their specific function
-func Limits(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for limits:
-
-limits:list        list resource limits for an app
-limits:set         set resource limits for an app
-limits:unset       unset resource limits for an app
-limits:specs       list specification information of the server
-limits:plans       list resource limit plans
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "limits:list":
-		return limitsList(argv, cmdr)
-	case "limits:set":
-		return limitSet(argv, cmdr)
-	case "limits:unset":
-		return limitUnset(argv, cmdr)
-	case "limits:specs":
-		return limitSpecs(argv, cmdr)
-	case "limits:plans":
-		return limitPlans(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "limits" {
-			argv[0] = "limits:list"
-			return limitsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func limitsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists resource limits for an application.
-
-Usage: drycc limits:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-  -v --version=<version>
-    the version for which the limit needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-
-	return cmdr.LimitsList(safeGetString(args, "--app"), version)
-}
-
-func limitSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets resource limits for an application.
-
-A resource limit is a finite resource within a pod which we can apply
-restrictions through Kubernetes.
-
-Usage: drycc limits:set <ptype>=<value>... [options]
-
-Arguments:
-  <ptype>
-    the process type as defined in your Procfile, such as 'web' or 'worker'.
-  <value>
-    The limit plan id to apply to the process type.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-
-Use 'drycc help [command]' to learn more.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	limits := args["<ptype>=<value>"].([]string)
-	return cmdr.LimitsSet(app, limits)
-}
-
-func limitUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets resource limits for an application.
-
-Usage: drycc limits:unset <ptype>... [options]
-
-Arguments:
-  <ptype>
-    the process type as defined in your Procfile, such as 'web' or 'worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	limits := args["<ptype>"].([]string)
-
-	return cmdr.LimitsUnset(app, limits)
-}
-
-func limitSpecs(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List all available limit specs.
-
-Usage: drycc limits:specs [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-  -k --keywords=<keywords>
-    search keywords separated by commas, matching must satisfy all of the specified.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-	keywords := safeGetString(args, "--keywords")
-
-	return cmdr.LimitsSpecs(keywords, results)
-}
-
-func limitPlans(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List all available limit plans.
-
-Usage: drycc limits:plans [options]
-
-Options:
-  -c --cpu=<cpu>
-    query plans that meet the specified number of cpu cores.
-  -m --memory=<memory>
-    query plans that meet the specified memory capacity, unit GiB.
-  --spec-id=<spec-id>
-    query plans that meet the specified spec id, see [specs] subcommand.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	specID := safeGetString(args, "--spec-id")
-	cpu := safeGetInt(args, "--cpu")
-	memory := safeGetInt(args, "--memory")
-
-	return cmdr.LimitsPlans(specID, cpu, memory, results)
-}
diff --git a/parser/limits_test.go b/parser/limits_test.go
deleted file mode 100644
index 23edc9a05..000000000
--- a/parser/limits_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) LimitsList(string, int) error {
-	return errors.New("limits:list")
-}
-
-func (d FakeDryccCmd) LimitsSet(string, []string) error {
-	return errors.New("limits:set")
-}
-
-func (d FakeDryccCmd) LimitsUnset(string, []string) error {
-	return errors.New("limits:unset")
-}
-func (d FakeDryccCmd) LimitsSpecs(string, int) error {
-	return errors.New("limits:specs")
-}
-func (d FakeDryccCmd) LimitsPlans(string, int, int, int) error {
-	return errors.New("limits:plans")
-}
-
-func TestLimits(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"limits:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits:set", "web=std1.large.c1m1"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits:set", "web=std1.large.c1m1 worker=std1.large.c1m1"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits:unset", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits:specs"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits:plans"},
-			expected: "",
-		},
-		{
-			args:     []string{"limits"},
-			expected: "limits:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Limits(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/parser_test.go b/parser/parser_test.go
deleted file mode 100644
index 7fa7cef1f..000000000
--- a/parser/parser_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package parser
-
-import "github.com/drycc/workflow-cli/cmd"
-
-// DryccCmd is an implementation of Commander.
-type FakeDryccCmd cmd.DryccCmd
-
-func (d FakeDryccCmd) Println(...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) Print(...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) Printf(string, ...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) PrintErrln(...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) PrintErr(...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) PrintErrf(string, ...interface{}) (int, error) {
-	return 1, nil
-}
-
-func (d FakeDryccCmd) ServicesAdd(string, string, string, string) error {
-	return nil
-}
-
-func (d FakeDryccCmd) ServicesList(string) error {
-	return nil
-}
-
-func (d FakeDryccCmd) ServicesRemove(string, string, string, int) error {
-	return nil
-}
diff --git a/parser/perms.go b/parser/perms.go
deleted file mode 100644
index 06e7057d8..000000000
--- a/parser/perms.go
+++ /dev/null
@@ -1,152 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Perms routes perms commands to their specific function.
-func Perms(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for perms:
-
-perms:list            list all user permission
-perms:add             create a user permission
-perms:update          update a user permission
-perms:remove          delete a user permission
-
-Use 'drycc help perms:[command]' to learn more.
-`
-
-	switch argv[0] {
-	case "perms:list":
-		return permsList(argv, cmdr)
-	case "perms:add":
-		return permCreate(argv, cmdr)
-	case "perms:update":
-		return permUpdate(argv, cmdr)
-	case "perms:remove":
-		return permDelete(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "perms" {
-			argv[0] = "perms:list"
-			return permsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func permsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List all user permissions.
-
-Usage: drycc perms:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	return cmdr.PermList(app, results)
-}
-
-func permCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Grant permissions to user.
-
-Usage: drycc perms:add <username> <permissions> [options]
-
-Arguments:
-  <username>
-    the name of the user.
-  <permissions>
-    comma-delimited list of permissions (view,change,delete).
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	username := safeGetString(args, "<username>")
-	permissions := safeGetString(args, "<permissions>")
-	return cmdr.PermCreate(app, username, permissions)
-}
-
-func permUpdate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Update permissions to user.
-
-Usage: drycc perms:update <username> <permissions> [options]
-
-Arguments:
-  <username>
-    the name of the user.
-  <permissions>
-    comma-delimited list of permissions (view,change,delete).
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	username := safeGetString(args, "<username>")
-	permissions := safeGetString(args, "<permissions>")
-	return cmdr.PermUpdate(app, username, permissions)
-}
-
-func permDelete(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Delete a user from the app.
-
-Usage: drycc perms:remove <username> [options]
-
-Arguments:
-  <username>
-    the name of the user.
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	username := safeGetString(args, "<username>")
-	return cmdr.PermDelete(app, username)
-}
diff --git a/parser/perms_test.go b/parser/perms_test.go
deleted file mode 100644
index eecac671d..000000000
--- a/parser/perms_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) PermList(string, int) error {
-	return errors.New("perms:list")
-}
-
-func (d FakeDryccCmd) PermCreate(string, string, string) error {
-	return errors.New("perms:add")
-}
-
-func (d FakeDryccCmd) PermUpdate(string, string, string) error {
-	return errors.New("perms:update")
-}
-
-func (d FakeDryccCmd) PermDelete(string, string) error {
-	return errors.New("perms:remove")
-}
-
-func TestPerms(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"perms:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"perms:add", "test-user", "view"},
-			expected: "",
-		},
-		{
-			args:     []string{"perms:update", "test-user", "view"},
-			expected: "",
-		},
-		{
-			args:     []string{"perms:remove", "test-user"},
-			expected: "",
-		},
-		{
-			args:     []string{"perms"},
-			expected: "perms:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Perms(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/ps.go b/parser/ps.go
deleted file mode 100644
index 1104b90b3..000000000
--- a/parser/ps.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package parser
-
-import (
-	"os"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-	"golang.org/x/exp/slices"
-)
-
-// Ps routes ps commands to their specific function.
-func Ps(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for processes:
-
-ps:list        list application processes
-ps:logs        print the logs for a container
-ps:exec        execute a command in a container
-ps:describe    print a detailed description of the selected process
-ps:delete      delete the selected processes
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "ps:list":
-		return psList(argv, cmdr)
-	case "ps:logs":
-		return psLogs(argv, cmdr)
-	case "ps:exec":
-		return psExec(argv, cmdr)
-	case "ps:describe":
-		return psDescribe(argv, cmdr)
-	case "ps:delete":
-		return psDelete(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "ps" {
-			argv[0] = "ps:list"
-			return psList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func psList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists processes servicing an application.
-
-Usage: drycc ps:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	// The 1000 is fake for now until API understands limits
-	return cmdr.PsList(safeGetString(args, "--app"), 1000)
-}
-
-func psLogs(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print the logs for a container in a pod or specified resource.
-
-Usage: drycc ps:logs <pod> [options]
-
-Arguments:
-  <pod> the pod name for the application.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -n --lines=<lines>
-    the number of lines to display, default to 300 lines, -1 showing all log lines.
-  -f --follow
-    specify if the logs should be streamed.
-  --container=<container>
-    print the logs of this container.
-  --previous
-    print the logs for the previous instance of the container in a pod if it exists.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	lines := safeGetInt(args, "--lines")
-	if lines < 0 {
-		lines = -1
-	} else if lines == 0 {
-		lines = 300
-	}
-	follow := safeGetBool(args, "--follow")
-	podID := safeGetString(args, "<pod>")
-	container := safeGetString(args, "--container")
-	previous := args["--previous"].(bool)
-
-	return cmdr.PsLogs(app, podID, lines, follow, container, previous)
-}
-
-func psExec(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Execute a command in a container.
-
-Usage: drycc ps:exec <pod> [options] -- <command>...
-
-Arguments:
-  <pod> the pod name for the application.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -t --tty
-    stdin is a TTY.
-  -i --stdin
-    pass stdin to the container.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	pod := safeGetString(args, "<pod>")
-	tty := args["--tty"].(bool)
-	stdin := args["--stdin"].(bool)
-	index := slices.Index(os.Args, "--")
-	command := os.Args[index+1:]
-	return cmdr.PsExec(app, pod, tty, stdin, command)
-}
-
-func psDescribe(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print a detailed description of the selected process.
-
-Usage: drycc ps:describe <pod> [options]
-
-Arguments:
-  <pod> the pod name for the application.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	pod := safeGetString(args, "<pod>")
-	return cmdr.PsDescribe(app, pod)
-}
-
-func psDelete(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Delete the selected processes.
-
-Usage: drycc ps:delete <pod>... [options]
-
-Arguments:
-  <pod> the pod name for the application.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	return cmdr.PsDelete(app, args["<pod>"].([]string))
-}
diff --git a/parser/ps_test.go b/parser/ps_test.go
deleted file mode 100644
index 0fcffea08..000000000
--- a/parser/ps_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) PsList(string, int) error {
-	return errors.New("ps:list")
-}
-
-func (d FakeDryccCmd) PsLogs(string, string, int, bool, string, bool) error {
-	return errors.New("ps:logs")
-}
-
-func (d FakeDryccCmd) PsExec(string, string, bool, bool, []string) error {
-	return errors.New("ps:exec")
-}
-
-func (d FakeDryccCmd) PsDescribe(string, string) error {
-	return errors.New("ps:describe")
-}
-
-func (d FakeDryccCmd) PsDelete(string, []string) error {
-	return errors.New("ps:delete")
-}
-func TestPs(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"ps:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps:logs", "myapp-web-111"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps:exec", "myapp-web-111", "-it", "--", "bash"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps:scale", "web", "5"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps:describe", "myapp-web-111"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps:delete", "myapp-web-111"},
-			expected: "",
-		},
-		{
-			args:     []string{"ps"},
-			expected: "ps:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Ps(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/pts.go b/parser/pts.go
deleted file mode 100644
index 94ee275f7..000000000
--- a/parser/pts.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Pts routes pts commands to their specific function.
-func Pts(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for processes:
-
-pts:list        list application process types
-pts:describe    print a detailed description of the selected process type
-pts:restart     restart an application or process types
-pts:scale       scale process types of replicas (e.g. web=4 worker=2)
-pts:clean       clean process types of not used
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "pts:list":
-		return ptsList(argv, cmdr)
-	case "pts:describe":
-		return ptsDescribe(argv, cmdr)
-	case "pts:restart":
-		return ptsRestart(argv, cmdr)
-	case "pts:scale":
-		return ptsScale(argv, cmdr)
-	case "pts:clean":
-		return ptsClean(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "pts" {
-			argv[0] = "pts:list"
-			return ptsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func ptsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists process types servicing an application.
-
-Usage: drycc pts:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	// The 1000 is fake for now until API understands limits
-	return cmdr.PtsList(safeGetString(args, "--app"), 1000)
-}
-
-func ptsDescribe(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print a detailed description of the selected process type.
-
-Usage: drycc pts:describe <ptype> [options]
-
-Arguments:
-  <ptype> the process name as defined in your Procfile, such as 'web'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "<ptype>")
-	return cmdr.PtsDescribe(app, ptype)
-}
-
-func ptsRestart(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Restart an application or process types.
-
-Usage: drycc pts:restart [<ptype>...] [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'web worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	apps := safeGetString(args, "--app")
-	confirm := safeGetString(args, "--confirm")
-	return cmdr.PtsRestart(apps, args["<ptype>"].([]string), confirm)
-}
-
-func ptsScale(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Scales an application's processes by type.
-
-Usage: drycc pts:scale <ptype>=<num>... [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'worker'.
-  <num>
-    the number of processes.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	apps := safeGetString(args, "--app")
-	return cmdr.PtsScale(apps, args["<ptype>=<num>"].([]string))
-}
-
-func ptsClean(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Clean process types of not used.
-
-Usage: drycc pts:clean <ptype>... [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	apps := safeGetString(args, "--app")
-	return cmdr.PtsClean(apps, args["<ptype>"].([]string))
-}
diff --git a/parser/pts_test.go b/parser/pts_test.go
deleted file mode 100644
index b84b6ac50..000000000
--- a/parser/pts_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) PtsList(string, int) error {
-	return errors.New("pts:list")
-}
-
-func (d FakeDryccCmd) PtsDescribe(string, string) error {
-	return errors.New("pts:describe")
-}
-
-func (d FakeDryccCmd) PtsScale(string, []string) error {
-	return errors.New("pts:scale")
-}
-
-func (d FakeDryccCmd) PtsRestart(string, []string, string) error {
-	return errors.New("pts:restart")
-}
-
-func (d FakeDryccCmd) PtsClean(string, []string) error {
-	return errors.New("pts:clean")
-}
-
-func TestPts(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"pts:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"pts:describe", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"pts:restart", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"pts:scale", "web", "5"},
-			expected: "",
-		},
-		{
-			args:     []string{"pts:clean", "task"},
-			expected: "",
-		},
-		{
-			args:     []string{"pts"},
-			expected: "pts:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Pts(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/registry.go b/parser/registry.go
deleted file mode 100644
index 6283e4810..000000000
--- a/parser/registry.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Registry routes registry commands to their specific function
-func Registry(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for registry:
-
-registry:list        list registry info for an app
-registry:set         set registry info for an app
-registry:unset       unset registry info for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "registry:list":
-		return registryList(argv, cmdr)
-	case "registry:set":
-		return registrySet(argv, cmdr)
-	case "registry:unset":
-		return registryUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "registry" {
-			argv[0] = "registry:list"
-			return registryList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func registryList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists registry information for an application.
-
-Usage: drycc registry:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-  -p --ptype=<ptype>
-    the ptype for registry.
-  -v --version=<version>
-    the version for which the registry needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-	ptype := safeGetValue(args, "--ptype", "")
-	return cmdr.RegistryList(safeGetString(args, "--app"), ptype, version)
-}
-
-func registrySet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets registry information for an application. These credentials are the same as those used for
-'podmain login' to the private registry.
-
-Usage: drycc registry:set <username> <password> [options]
-
-Arguments:
-  <username>
-    the username of the registry.
-  <password>
-    the password of the registry.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptype=<ptype>
-    the ptype for registry, default[web].
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	u := safeGetString(args, "<username>")
-	p := safeGetString(args, "<password>")
-	ptype := safeGetValue(args, "--ptype", "web")
-	return cmdr.RegistrySet(app, ptype, u, p)
-}
-
-func registryUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets registry information for an application.
-
-Usage: drycc registry:unset [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptype=<ptype>
-    the ptype for registry, default[web].
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetValue(args, "--ptype", "web")
-	return cmdr.RegistryUnset(app, ptype)
-}
diff --git a/parser/registry_test.go b/parser/registry_test.go
deleted file mode 100644
index 6454c2bf9..000000000
--- a/parser/registry_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) RegistryList(string, string, int) error {
-	return errors.New("registry:list")
-}
-
-func (d FakeDryccCmd) RegistrySet(string, string, string, string) error {
-	return errors.New("registry:set")
-}
-
-func (d FakeDryccCmd) RegistryUnset(string, string) error {
-	return errors.New("registry:unset")
-}
-
-func TestRegistry(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"registry:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"registry:set", "username", "password"},
-			expected: "",
-		},
-		{
-			args:     []string{"registry:unset"},
-			expected: "",
-		},
-		{
-			args:     []string{"registry"},
-			expected: "registry:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Registry(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/releases.go b/parser/releases.go
deleted file mode 100644
index 7fbc9c30a..000000000
--- a/parser/releases.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package parser
-
-import (
-	"fmt"
-	"strconv"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Releases routes releases commands to their specific function.
-func Releases(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for releases:
-
-releases:list        list an application's release history
-releases:info        print information about a specific release
-releases:deploy      deploy the latest release by process types
-releases:rollback    return to a previous release
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "releases:list":
-		return releasesList(argv, cmdr)
-	case "releases:info":
-		return releasesInfo(argv, cmdr)
-	case "releases:deploy":
-		return releasesDeploy(argv, cmdr)
-	case "releases:rollback":
-		return releasesRollback(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "releases" {
-			argv[0] = "releases:list"
-			return releasesList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func releasesList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists release history for an application.
-
-Usage: drycc releases:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -p --ptypes=<ptypes>
-    the processes name as defined in your Procfile, comma separated.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptypes := safeGetString(args, "--ptypes")
-
-	return cmdr.ReleasesList(app, ptypes, results)
-}
-
-func releasesInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about a particular release.
-
-Usage: drycc releases:info <version> [options]
-
-Arguments:
-  <version>
-    the release of the application, such as 'v1'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	version, err := versionFromString(args["<version>"].(string))
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.ReleasesInfo(app, version)
-}
-
-func releasesDeploy(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Deploy the latest release by process types.
-
-Usage: drycc releases:deploy [<ptype>...] [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'web worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -f --force
-    force deploy.
-  --confirm=yes
-    to proceed, type "yes".
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	apps := safeGetString(args, "--app")
-	confirm := safeGetString(args, "--confirm")
-	force := args["--force"].(bool)
-	return cmdr.ReleasesDeploy(apps, args["<ptype>"].([]string), force, confirm)
-}
-
-func releasesRollback(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Rolls back to a previous application release.
-
-Usage: drycc releases:rollback [<ptype>...] [<version>] [options]
-
-Arguments:
-<ptype>
-    the process name as defined in your Procfile, such as 'web'.
-<version>
-    the release of the application, such as 'v1'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	var version int
-
-	if args["<version>"] == nil {
-		version = -1
-	} else {
-		version, err = versionFromString(args["<version>"].(string))
-
-		if err != nil {
-			return err
-		}
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.ReleasesRollback(app, args["<ptype>"].([]string), version)
-}
-
-func versionFromString(version string) (int, error) {
-	if version[:1] == "v" {
-		if len(version) < 2 {
-			return -1, fmt.Errorf("%s is not in the form 'v#'", version)
-		}
-
-		return strconv.Atoi(version[1:])
-	}
-
-	return strconv.Atoi(version)
-}
diff --git a/parser/releases_test.go b/parser/releases_test.go
deleted file mode 100644
index c99231a9e..000000000
--- a/parser/releases_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) ReleasesList(string, string, int) error {
-	return errors.New("releases:list")
-}
-
-func (d FakeDryccCmd) ReleasesInfo(string, int) error {
-	return errors.New("releases:info")
-}
-
-func (d FakeDryccCmd) ReleasesDeploy(string, []string, bool, string) error {
-	return errors.New("releases:deploy")
-}
-
-func (d FakeDryccCmd) ReleasesRollback(string, []string, int) error {
-	return errors.New("releases:rollback")
-}
-
-func TestReleases(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"releases:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"releases:info", "v1"},
-			expected: "",
-		},
-		{
-			args:     []string{"releases:deploy", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"releases:rollback"},
-			expected: "",
-		},
-		{
-			args:     []string{"releases:rollback", "v1"},
-			expected: "",
-		},
-		{
-			args:     []string{"releases"},
-			expected: "releases:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Releases(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/resources.go b/parser/resources.go
deleted file mode 100644
index 55343ab56..000000000
--- a/parser/resources.go
+++ /dev/null
@@ -1,335 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Resources commands to their specific function.
-func Resources(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for resources:
-
-resources:services         list all available resource services
-resources:plans            list all available plans for an resource services
-resources:create           create a resource for the application
-resources:list             list resources in the application
-resources:describe         get a resource detail info in the application
-resources:update           update a resource from the application
-resources:destroy          delete a resource from the applicationa
-resources:bind             bind a resource to servicebroker
-resources:unbind           unbind a resource from servicebroker
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "resources:services":
-		return resourcesServices(argv, cmdr)
-	case "resources:plans":
-		return resourcesPlans(argv, cmdr)
-	case "resources:create":
-		return resourcesCreate(argv, cmdr)
-	case "resources:list":
-		return resourcesList(argv, cmdr)
-	case "resources:describe":
-		return resourceGet(argv, cmdr)
-	case "resources:update":
-		return resourcePut(argv, cmdr)
-	case "resources:destroy":
-		return resourceDelete(argv, cmdr)
-	case "resources:bind":
-		return resourceBind(argv, cmdr)
-	case "resources:unbind":
-		return resourceUnbind(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "resources" {
-			argv[0] = "resources:list"
-			return resourcesList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func resourcesServices(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List all available resource services.
-
-Usage: drycc resources:services [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.ResourcesServices(results)
-}
-
-func resourcesPlans(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List all available plans for an resource services.
-
-Usage: drycc resources:plans <service> [options]
-
-Arguments:
-  <service>
-    the service name for plans.
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	service := safeGetString(args, "<service>")
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.ResourcesPlans(service, results)
-}
-
-func resourcesCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Create a resource for the application.
-
-Usage: drycc resources:create <plan> <name> [<param>=<value>...] [options]
-
-Arguments:
-  <plan>
-    the resource's plan, pattern: <service_name>:<plan_name>.
-  <name>
-    this resource instance alias.
-  <param>
-    the resource instance parameters key.
-  <value>
-    the resource instance parameters value.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -f --values=<values_file>
-    specify values in a YAML file. If set, params will be discard.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	values := safeGetString(args, "--values")
-	plan := safeGetString(args, "<plan>")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.ResourcesCreate(app, plan, name, args["<param>=<value>"].([]string), values)
-}
-
-func resourcesList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List resources in the application.
-
-Usage: drycc resources:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-
-	return cmdr.ResourcesList(app, results)
-}
-
-func resourceGet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Get a resource's detail in the application.
-
-Usage: drycc resources:describe <name> [options]
-
-Arguments:
-  <name>
-    this resource instance alias.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --details
-    show extra details provided to resource.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	details := safeGetBool(args, "--details")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.ResourceGet(app, name, details)
-}
-
-func resourcePut(argv []string, cmdr cmd.Commander) error {
-
-	usage := `
-Update a resource from the application.
-
-Usage: drycc resources:update <plan> <name> [<param>=<value>...] [options]
-
-Arguments:
-  <plan>
-    the resource's plan, pattern: <service_name>:<plan_name>.
-  <name>
-    this resource instance alias.
-  <param>
-    the resource instance parameters key.
-  <value>
-    the resource instance parameters value.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -f --values=<values_file>
-	specify values in a YAML file. If set, params will be discard.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	values := safeGetString(args, "--values")
-	plan := safeGetString(args, "<plan>")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.ResourcePut(app, plan, name, args["<param>=<value>"].([]string), values)
-}
-
-func resourceDelete(argv []string, cmdr cmd.Commander) error {
-
-	usage := `
-Delete a resource from the application.
-
-Usage: drycc resources:destroy <name> [options]
-
-Arguments:
-  <name>
-    the resource instance alias name to be removed.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --confirm=<resource>
-    skips the prompt for the resource name. <resource> is the uniquely identifiable
-    name for the resource.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	confirm := safeGetString(args, "--confirm")
-
-	return cmdr.ResourceDelete(app, name, confirm)
-}
-
-func resourceBind(argv []string, cmdr cmd.Commander) error {
-	usage := `
-bind a resource for an application.
-
-Usage: drycc resources:bind <name> [options]
-
-Arguments:
-  <name>
-    the resource instance alias name.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.ResourceBind(app, name)
-}
-
-func resourceUnbind(argv []string, cmdr cmd.Commander) error {
-	usage := `
-unbind a resources for an application.
-
-Usage: drycc resources:unbind <name> [options]
-
-Arguments:
-  <name>
-    the resource instance alias name.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.ResourceUnbind(app, name)
-}
diff --git a/parser/resources_test.go b/parser/resources_test.go
deleted file mode 100644
index 357a96354..000000000
--- a/parser/resources_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) ResourcesServices(int) error {
-	return errors.New("resources:services")
-}
-
-func (d FakeDryccCmd) ResourcesPlans(string, int) error {
-	return errors.New("resources:plans")
-}
-
-func (d FakeDryccCmd) ResourcesCreate(string, string, string, []string, string) error {
-	return errors.New("resources:create")
-}
-
-func (d FakeDryccCmd) ResourcesList(string, int) error {
-	return errors.New("resources:list")
-}
-
-func (d FakeDryccCmd) ResourceGet(string, string, bool) error {
-	return errors.New("resources:describe")
-}
-
-func (d FakeDryccCmd) ResourcePut(string, string, string, []string, string) error {
-	return errors.New("resources:update")
-}
-
-func (d FakeDryccCmd) ResourceDelete(string, string, string) error {
-	return errors.New("resources:destroy")
-}
-
-func (d FakeDryccCmd) ResourceBind(string, string) error {
-	return errors.New("resources:bind")
-}
-
-func (d FakeDryccCmd) ResourceUnbind(string, string) error {
-	return errors.New("resources:unbind")
-}
-
-func TestResources(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"resources:services"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:plans", "mysql"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:create", "mysql:5.6", "mysql", "key1=value1"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:describe", "mysql"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:update", "mysql:5.7", "mysql", "key1=value2"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:destroy", "mysql"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:bind", "mysql"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources:unbind", "mysql"},
-			expected: "",
-		},
-		{
-			args:     []string{"resources"},
-			expected: "resources:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Resources(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/routes.go b/parser/routes.go
deleted file mode 100644
index 26761d619..000000000
--- a/parser/routes.go
+++ /dev/null
@@ -1,286 +0,0 @@
-package parser
-
-import (
-	"errors"
-	"regexp"
-	"strconv"
-
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/controller-sdk-go/api"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Routes routes commands to their specific function.
-func Routes(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for routes:
-
-routes:add           create a route for an application
-routes:list          list application routes
-routes:get           get route rules
-routes:set           set route rules
-routes:attach        attach to gateway
-routes:detach        detach to gateway
-routes:remove        remove routes from an application
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "routes:add":
-		return routesCreate(argv, cmdr)
-	case "routes:list":
-		return routesList(argv, cmdr)
-	case "routes:get":
-		return routesGet(argv, cmdr)
-	case "routes:set":
-		return routesSet(argv, cmdr)
-	case "routes:attach":
-		return routesAttach(argv, cmdr)
-	case "routes:detach":
-		return routesDetach(argv, cmdr)
-	case "routes:remove":
-		return routesRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "routes" {
-			argv[0] = "routes:list"
-			return routesList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func routesCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Creates routes for an application, provides a way to route requests.
-
-Usage: drycc routes:add <name> <kind> [<backend>...] [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-  <kind>
-    the route kind, range: HTTPRoute,TCPRoute,UDPRoute,GRPCRoute,TLSRoute.
-  <backend>
-    the route's backend, pattern: <service>:<port>,<weight>.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	kind := safeGetString(args, "<kind>")
-	var backendRefs []api.BackendRefRequest
-	if backends, ok := args["<backend>"].([]string); ok && len(backends) > 0 {
-		for _, backendRef := range backends {
-			params := regexp.MustCompile("[:,]").Split(backendRef, -1)
-			if len(params) != 3 {
-				return errors.New("backend params format error")
-			}
-			port, err := strconv.ParseInt(params[1], 10, 32)
-			if err != nil {
-				return err
-			}
-			weight, err := strconv.ParseInt(params[2], 10, 32)
-			if err != nil {
-				return err
-			}
-			backendRefs = append(backendRefs, api.BackendRefRequest{
-				Kind: "Service", Name: params[0], Port: int32(port), Weight: int32(weight),
-			})
-		}
-	} else {
-		return errors.New("backend is required")
-	}
-	return cmdr.RoutesCreate(app, name, kind, backendRefs...)
-}
-
-func routesList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List routes for an application.
-
-Usage: drycc routes:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	results, err := responseLimit(safeGetString(args, "--limit"))
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.RoutesList(app, results)
-}
-
-func routesGet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Get route rules for an application.
-
-Usage: drycc routes:get <name> [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.RoutesGet(app, name)
-}
-
-func routesSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Set route rules for an application.
-
-Usage: drycc routes:set <name> --rules-file=<rules-file> [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-  <rules-file>
-    rules-file is the file name of rule to apply.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	rulesFile := safeGetString(args, "--rules-file")
-
-	return cmdr.RoutesSet(app, name, rulesFile)
-}
-
-func routesAttach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Attach a route for an application.
-
-Usage: drycc routes:attach <name> --gateway=<gateway> --port=<port> [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-  <gateway>
-    the unique name of the gaetway.
-  <port>
-    port is the network port, the gateway listener expects to receive.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	port := safeGetInt(args, "--port")
-	gateway := safeGetString(args, "--gateway")
-
-	return cmdr.RoutesAttach(app, name, port, gateway)
-}
-
-func routesDetach(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Detach a route for an application.
-
-Usage: drycc routes:detach <name> --gateway=<gateway> --port=<port> [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-  <gateway>
-    the unique name of the gaetway.
-  <port>
-    port is the network port, the gateway listener expects to receive.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	port := safeGetInt(args, "--port")
-	gateway := safeGetString(args, "--gateway")
-
-	return cmdr.RoutesDetach(app, name, port, gateway)
-}
-
-func routesRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Delete a route from the application.
-
-Usage: drycc routes:remove <name> [options]
-
-Arguments:
-  <name>
-    the unique name of the route.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.RoutesRemove(app, name)
-}
diff --git a/parser/routes_test.go b/parser/routes_test.go
deleted file mode 100644
index baf5be22e..000000000
--- a/parser/routes_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/controller-sdk-go/api"
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-func (d FakeDryccCmd) RoutesCreate(string, string, string, ...api.BackendRefRequest) error {
-	return errors.New("routes:add")
-}
-
-func (d FakeDryccCmd) RoutesList(string, int) error {
-	return errors.New("routes:list")
-}
-
-func (d FakeDryccCmd) RoutesGet(string, string) error {
-	return errors.New("routes:get")
-}
-
-func (d FakeDryccCmd) RoutesSet(string, string, string) error {
-	return errors.New("routes:Set")
-}
-
-func (d FakeDryccCmd) RoutesAttach(string, string, int, string) error {
-	return errors.New("routes:attach")
-}
-
-func (d FakeDryccCmd) RoutesDetach(string, string, int, string) error {
-	return errors.New("routes:detach")
-}
-
-func (d FakeDryccCmd) RoutesRemove(string, string) error {
-	return errors.New("routes:remove")
-}
-
-func TestRoutes(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"routes:add", "example", "http", "example:80,100"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes:get", "example"},
-			expected: "",
-		}, {
-			args:     []string{"routes:set", "example", "--rules-file=rules,json"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes:attach", "example", "--port=80", "--gateway=example"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes:detach", "example", "--port=80", "--gateway=example"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes:remove", "example"},
-			expected: "",
-		},
-		{
-			args:     []string{"routes"},
-			expected: "routes:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Routes(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/routing.go b/parser/routing.go
deleted file mode 100644
index a0bba436d..000000000
--- a/parser/routing.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Routing displays all relevant commands for `drycc routing`.
-func Routing(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for routing:
-
-routing:info       view routability of an application
-routing:enable     enable routing for an app
-routing:disable    disable routing for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "routing:info":
-		return routingInfo(argv, cmdr)
-	case "routing:enable":
-		return routingEnable(argv, cmdr)
-	case "routing:disable":
-		return routingDisable(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "routing" {
-			argv[0] = "routing:info"
-			return routingInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func routingInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about the current application's routability.
-
-Usage: drycc routing:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.RoutingInfo(safeGetString(args, "--app"))
-}
-
-func routingEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enables routability for an app.
-
-Usage: drycc routing:enable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.RoutingEnable(safeGetString(args, "--app"))
-}
-
-func routingDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disables routability for an app.
-
-Usage: drycc routing:disable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.RoutingDisable(safeGetString(args, "--app"))
-}
diff --git a/parser/routing_test.go b/parser/routing_test.go
deleted file mode 100644
index 2ee3c9914..000000000
--- a/parser/routing_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) RoutingInfo(string) error {
-	return errors.New("routing:info")
-}
-
-func (d FakeDryccCmd) RoutingEnable(string) error {
-	return errors.New("routing:enable")
-}
-
-func (d FakeDryccCmd) RoutingDisable(string) error {
-	return errors.New("routing:disable")
-}
-
-func TestRouting(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"routing:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"routing:enable"},
-			expected: "",
-		},
-		{
-			args:     []string{"routing:disable"},
-			expected: "",
-		},
-		{
-			args:     []string{"routing"},
-			expected: "routing:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Routing(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/services.go b/parser/services.go
deleted file mode 100644
index a382dfe78..000000000
--- a/parser/services.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Services routes service commands to their specific function.
-func Services(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for services:
-
-services:add           create service for an application
-services:list          list application services
-services:remove        remove service from an application
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "services:add":
-		return servicesAdd(argv, cmdr)
-	case "services:list":
-		return servicesList(argv, cmdr)
-	case "services:remove":
-		return servicesRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "services" {
-			argv[0] = "services:list"
-			return servicesList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func servicesAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Creates extra service for an application and binds it to specific route of the main app domain.
-
-Usage: drycc services:add <ptype> <port>:<target> [options]
-
-Arguments:
-  <ptype>
-    procfile type which should handle the request, e.g. webhooks (should be bind to the port PORT)
-    only single extra service per Porcfile type could be created.
-  <port>
-    the port that will be exposed by this service.
-  <target>
-    number or name of the port to access on the pods targeted by the service.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --protocol=<protocol>
-    the IP protocol for this port. Supports TCP, UDP, and SCTP. Default is TCP.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "<ptype>")
-	protocol := safeGetString(args, "--protocol")
-	ports := safeGetString(args, "<port>:<target>")
-	return cmdr.ServicesAdd(app, ptype, ports, protocol)
-}
-
-func servicesList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists extra services for an application.
-
-Usage: drycc services:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-
-	return cmdr.ServicesList(app)
-}
-
-func servicesRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Deletes specific extra service for application.
-
-Usage: drycc services:remove <ptype> <port> [options]
-
-Arguments:
-  <ptype>
-    procfile type which should handle the request, e.g. webhooks (should be bind to the port PORT).
-    Only single extra service per Porcfile type could be created.
-  <port>
-    the port exposed by this service.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --protocol=<protocol>
-    the IP protocol for this port. Supports TCP, UDP, and SCTP. Default is TCP.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	ptype := safeGetString(args, "<ptype>")
-	protocol := safeGetString(args, "--protocol")
-	port := safeGetInt(args, "<port>")
-
-	return cmdr.ServicesRemove(app, ptype, protocol, port)
-}
diff --git a/parser/shortcuts.go b/parser/shortcuts.go
deleted file mode 100644
index 5b3a84115..000000000
--- a/parser/shortcuts.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Shortcuts displays all relevant shortcuts for the CLI.
-func Shortcuts(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for shortcuts:
-
-shortcuts:list       list all relevant shortcuts for the CLI
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "shortcuts:list":
-		return shortcutsList(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "shortcuts" {
-			argv[0] = "shortcuts:list"
-			return shortcutsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func shortcutsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists all relevant shortcuts for the CLI.
-
-Usage: drycc shortcuts:list
-`
-
-	_, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.ShortcutsList()
-}
diff --git a/parser/shortcuts_test.go b/parser/shortcuts_test.go
deleted file mode 100644
index 303d5feba..000000000
--- a/parser/shortcuts_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) ShortcutsList() error {
-	return errors.New("shortcuts:list")
-}
-
-func TestShortcuts(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"shortcuts:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"shortcuts"},
-			expected: "shortcuts:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Shortcuts(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/tags.go b/parser/tags.go
deleted file mode 100644
index 2aeac1f2e..000000000
--- a/parser/tags.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Tags routes tags commands to their specific function
-func Tags(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for tags:
-
-tags:list        list tags for an app
-tags:set         set tags for an app
-tags:unset       unset tags for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "tags:list":
-		return tagsList(argv, cmdr)
-	case "tags:set":
-		return tagsSet(argv, cmdr)
-	case "tags:unset":
-		return tagsUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "tags" {
-			argv[0] = "tags:list"
-			return tagsList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func tagsList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists tags for an application.
-
-Usage: drycc tags:list <ptype> [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'web worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-  -v --version=<version>
-    the version for which the tag needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	ptype := safeGetString(args, "<ptype>")
-	appName := safeGetString(args, "--app")
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-	return cmdr.TagsList(appName, ptype, version)
-}
-
-func tagsSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets tags for an application.
-
-A tag is a key/value pair used to tag an application's containers and is passed to the
-scheduler. This is often used to restrict workloads to specific hosts matching the
-scheduler-configured metadata.
-
-Usage: drycc tags:set <ptype> <key>=<value>... [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'web worker'.
-  <key>
-    the tag key, for example: "environ" or "rack".
-  <value>
-    the tag value, for example: "prod" or "1".
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	ptype := safeGetString(args, "<ptype>")
-	app := safeGetString(args, "--app")
-	tags := args["<key>=<value>"].([]string)
-
-	return cmdr.TagsSet(app, ptype, tags)
-}
-
-func tagsUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets tags for an application.
-
-Usage: drycc tags:unset <ptype> <key>... [options]
-
-Arguments:
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'web worker'.
-  <key>
-    the tag key to unset, for example: "environ" or "rack".
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	ptype := safeGetString(args, "<ptype>")
-	app := safeGetString(args, "--app")
-	tags := args["<key>"].([]string)
-
-	return cmdr.TagsUnset(app, ptype, tags)
-}
diff --git a/parser/tags_test.go b/parser/tags_test.go
deleted file mode 100644
index 4d5603c3b..000000000
--- a/parser/tags_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) TagsList(string, string, int) error {
-	return errors.New("tags:list")
-}
-
-func (d FakeDryccCmd) TagsSet(string, string, []string) error {
-	return errors.New("tags:set")
-}
-
-func (d FakeDryccCmd) TagsUnset(string, string, []string) error {
-	return errors.New("tags:unset")
-}
-
-func TestTags(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"tags:list", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"tags:set", "web", "environ", "prod"},
-			expected: "",
-		},
-		{
-			args:     []string{"tags:unset", "web", "environ"},
-			expected: "",
-		},
-		{
-			args:     []string{"tags", "web"},
-			expected: "tags:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Tags(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/timeouts.go b/parser/timeouts.go
deleted file mode 100644
index 07847dd62..000000000
--- a/parser/timeouts.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Timeouts routes timeouts commands to their specific function
-func Timeouts(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for timeouts:
-
-timeouts:list        list resource timeouts for an app
-timeouts:set         set resource timeouts for an app
-timeouts:unset       unset resource timeouts for an app
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "timeouts:list":
-		return timeoutList(argv, cmdr)
-	case "timeouts:set":
-		return timeoutSet(argv, cmdr)
-	case "timeouts:unset":
-		return timeoutUnset(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "timeouts" {
-			argv[0] = "timeouts:list"
-			return timeoutList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func timeoutList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists resource timeouts for an application.
-
-Usage: drycc timeouts:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name of the application.
-  -v --version=<version>
-    the version for which the timeout needs to be listed.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	var version int
-	if safeGetString(args, "--version") != "" {
-		if version, err = versionFromString(safeGetString(args, "--version")); err != nil {
-			return err
-		}
-	}
-	return cmdr.TimeoutsList(safeGetString(args, "--app"), version)
-}
-
-func timeoutSet(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Sets termination grace period for an application.
-
-Usage: drycc timeouts:set <ptype>=<value>... [options]
-
-Arguments:
-  <ptype>
-    the process type as defined in your Procfile, such as 'web' or 'worker'.
-  <value>
-    The value to apply to the process type in seconds.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	timeouts := args["<ptype>=<value>"].([]string)
-
-	return cmdr.TimeoutsSet(app, timeouts)
-}
-
-func timeoutUnset(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unsets timeouts for an application. Default value (30s) or set by drycc controller
-
-Usage: drycc timeouts:unset <ptype>... [options]
-
-Arguments:
-  <ptype>
-    the process type as defined in your Procfile, such as 'web' or 'worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	timeouts := args["<ptype>"].([]string)
-
-	return cmdr.TimeoutsUnset(app, timeouts)
-}
diff --git a/parser/timeouts_test.go b/parser/timeouts_test.go
deleted file mode 100644
index 92c072495..000000000
--- a/parser/timeouts_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) TimeoutsList(string, int) error {
-	return errors.New("timeouts:list")
-}
-
-func (d FakeDryccCmd) TimeoutsSet(string, []string) error {
-	return errors.New("timeouts:set")
-}
-
-func (d FakeDryccCmd) TimeoutsUnset(string, []string) error {
-	return errors.New("timeouts:unset")
-}
-
-func TestTimeouts(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"timeouts:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"timeouts:set", "web=100"},
-			expected: "",
-		},
-		{
-			args:     []string{"timeouts:set", "web=100 worker=200"},
-			expected: "",
-		},
-		{
-			args:     []string{"timeouts:unset", "web"},
-			expected: "",
-		},
-		{
-			args:     []string{"timeouts"},
-			expected: "timeouts:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Timeouts(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/tls.go b/parser/tls.go
deleted file mode 100644
index af3886aad..000000000
--- a/parser/tls.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// TLS routes tls commands to their specific function.
-func TLS(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for tls:
-
-tls:info              view info about an application's TLS settings
-tls:auto:issuer       set automatic certificate management environment issuer
-tls:auto:enable       enable automatic certificate management environment
-tls:auto:disable      disable automatic certificate management environment
-tls:force:enable      enable https redirects all your visitor requests from http to https
-tls:force:disable     disable https redirects all your visitor requests from http to https
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "tls:info":
-		return tlsInfo(argv, cmdr)
-	case "tls:auto:issuer":
-		return tlsAutoIssuer(argv, cmdr)
-	case "tls:auto:enable":
-		return tlsAutoEnable(argv, cmdr)
-	case "tls:auto:disable":
-		return tlsAutoDisable(argv, cmdr)
-	case "tls:force:enable":
-		return tlsForceEnable(argv, cmdr)
-	case "tls:force:disable":
-		return tlsForceDisable(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "tls" {
-			argv[0] = "tls:info"
-			return tlsInfo(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func tlsInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Prints info about the current application's TLS settings.
-
-Usage: drycc tls:info [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.TLSInfo(safeGetString(args, "--app"))
-}
-
-func tlsForceEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enable the router to enforce https-only requests to the current application.
-
-Usage: drycc tls:force:enable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.TLSForceEnable(safeGetString(args, "--app"))
-}
-
-func tlsForceDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disable the router from enforcing https-only requests to the current application.
-
-Usage: drycc tls:force:disable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.TLSForceDisable(safeGetString(args, "--app"))
-}
-
-func tlsAutoEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enable certs-auto requests to current application.
-
-Usage: drycc tls:auto:enable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.TLSAutoEnable(safeGetString(args, "--app"))
-}
-
-func tlsAutoDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disable certs-auto requests to current application.
-
-Usage: drycc tls:auto:disable [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.TLSAutoDisable(safeGetString(args, "--app"))
-}
-
-func tlsAutoIssuer(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disable certs-auto requests to current application.
-
-Usage: drycc tls:auto:issuer --email=<email> --server=<server> --key-id=<key-id> --key-secret=<key-secret> [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  --email=<email>
-    the email address to be associated with the ACME account.
-  --server=<server>
-    Server is the URL used to access the ACME server's 'directory' endpoint.
-  --key-id=<key-id>
-    keyID is the ID of the CA key that the External Account is bound to.
-  --key-secret=<key-secret>
-    keySecret holds the symmetric MAC key of the External Account Binding.
-
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-	email := safeGetString(args, "--email")
-	server := safeGetString(args, "--server")
-	keyID := safeGetString(args, "--key-id")
-	keySecret := safeGetString(args, "--key-secret")
-
-	return cmdr.TLSAutoIssuer(app, email, server, keyID, keySecret)
-}
diff --git a/parser/tls_test.go b/parser/tls_test.go
deleted file mode 100644
index d671a2a38..000000000
--- a/parser/tls_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) TLSInfo(string) error {
-	return errors.New("tls:info")
-}
-
-func (d FakeDryccCmd) TLSForceEnable(string) error {
-	return errors.New("tls:force:enable")
-}
-
-func (d FakeDryccCmd) TLSForceDisable(string) error {
-	return errors.New("tls:force:disable")
-}
-
-func (d FakeDryccCmd) TLSAutoEnable(string) error {
-	return errors.New("tls:auto:disable")
-}
-
-func (d FakeDryccCmd) TLSAutoDisable(string) error {
-	return errors.New("tls:auto:disable")
-}
-
-func (d FakeDryccCmd) TLSAutoIssuer(string, string, string, string, string) error {
-	return errors.New("tls:auto:issuer")
-}
-
-func TestTLS(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"tls:info"},
-			expected: "",
-		},
-		{
-			args:     []string{"tls:force:enable"},
-			expected: "",
-		},
-		{
-			args:     []string{"tls:force:disable"},
-			expected: "",
-		},
-		{
-			args:     []string{"tls:auto:issuer", "--email=drycc@drycc.cc", "--server=https://acme.zerossl.com/v2/DV90", "--key-id=keyID", "--key-secret=keySecret"},
-			expected: "",
-		},
-		{
-			args:     []string{"tls"},
-			expected: "tls:info",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = TLS(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/tokens.go b/parser/tokens.go
deleted file mode 100644
index 3c8afa530..000000000
--- a/parser/tokens.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Tokens commands to the specific function.
-func Tokens(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for tokens:
-
-tokens:list          lists tokens visible to the current controller
-tokens:add           add a token for controller authentication
-tokens:remove        remove a token for controller authentication
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "tokens:list":
-		return tokensList(argv, cmdr)
-	case "tokens:add":
-		return tokensAdd(argv, cmdr)
-	case "tokens:remove":
-		return tokensRemove(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-		if argv[0] == "tokens" {
-			argv[0] = "tokens:list"
-			return tokensList(argv, cmdr)
-		}
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func tokensList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists tokens visible to the current controller.
-
-Usage: drycc tokens:list [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	results, err := responseLimit(safeGetString(args, "--limit"))
-	if err != nil {
-		return err
-	}
-	return cmdr.TokensList(results)
-}
-
-func tokensAdd(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Add a token for controller authentication.
-
-Usage: drycc tokens:add <alias> [options]
-
-Arguments:
-  <alias>
-    provide a alias for controller authentication token.
-
-Options:
-  -u --username=<username>
-    provide a username for the account.
-  -p --password=<password>
-    provide a password for the account.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-	alias := safeGetString(args, "<alias>")
-	username := safeGetString(args, "--username")
-	password := safeGetString(args, "--password")
-	_, err = cmdr.TokensAdd(nil, username, password, alias, "", true)
-	return err
-}
-
-func tokensRemove(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Remove a token for controller authentication.
-
-Usage: drycc tokens:remove <id>
-
-Arguments:
-  <id>
-    the id of the token for controller authentication.
-
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	return cmdr.TokensRemove(safeGetString(args, "<id>"), "")
-}
diff --git a/parser/tokens_test.go b/parser/tokens_test.go
deleted file mode 100644
index ba6335a38..000000000
--- a/parser/tokens_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	drycc "github.com/drycc/controller-sdk-go"
-	"github.com/drycc/controller-sdk-go/api"
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) TokensList(int) error {
-	return errors.New("tokens:list")
-}
-
-func (d FakeDryccCmd) TokensAdd(*drycc.Client, string, string, string, string, bool) (*api.AuthTokenResponse, error) {
-	return nil, errors.New("tokens:add")
-}
-
-func (d FakeDryccCmd) TokensRemove(string, string) error {
-	return errors.New("tokens:remove")
-}
-
-func TestTokens(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"tokens:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"gateways:add", "alias"},
-			expected: "",
-		},
-		{
-			args:     []string{"gateways:remove", "87587952-0b78-4ecb-ac74-1d9c46197efe"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Tokens(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/update.go b/parser/update.go
deleted file mode 100644
index 287379c08..000000000
--- a/parser/update.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Version displays the client version
-func Update(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Update workflow cli to latest release.
-
-Usage: drycc update [options]
-
-Options:
-  -d --dry-run
-    simulate an update, only print the version info.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	return cmdr.Update(safeGetBool(args, "--dry-run"))
-}
diff --git a/parser/update_test.go b/parser/update_test.go
deleted file mode 100644
index 64e20a234..000000000
--- a/parser/update_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) Update(bool) error {
-	return errors.New("update")
-}
-
-func TestUpdate(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"update"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Update(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/users.go b/parser/users.go
deleted file mode 100644
index 1e7091fd4..000000000
--- a/parser/users.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Users routes user commands to the specific function.
-func Users(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for users:
-
-users:list        list all registered users
-users:enable      enable a user
-users:disable     disable a user
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "users:list":
-		return usersList(argv, cmdr)
-	case "users:enable":
-		return usersEnable(argv, cmdr)
-	case "users:disable":
-		return usersDisable(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "users" {
-			argv[0] = "users:list"
-			return usersList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func usersList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Lists all registered users. Workflow administrators will be marked with a *.
-Requires admin privileges.
-
-Usage: drycc users:list [options]
-
-Options:
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-
-	return cmdr.UsersList(results)
-}
-
-func usersEnable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Enable a user when his status is disabled.
-Requires admin privileges.
-
-Usage: drycc users:enable <username>
-
-Arguments:
-  <username>
-    the username you want to enable.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	username := safeGetString(args, "<username>")
-
-	return cmdr.UsersEnable(username)
-}
-
-func usersDisable(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Disable a user when his status is enabled.
-Requires admin privileges.
-
-Usage: drycc users:disable <username>
-
-Arguments:
-  <username>
-    the username you want to disable.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-	username := safeGetString(args, "<username>")
-
-	return cmdr.UsersDisable(username)
-}
diff --git a/parser/users_test.go b/parser/users_test.go
deleted file mode 100644
index b3a7aa622..000000000
--- a/parser/users_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) UsersList(int) error {
-	return errors.New("users:list")
-}
-
-func (d FakeDryccCmd) UsersEnable(string) error {
-	return errors.New("users:enable")
-}
-
-func (d FakeDryccCmd) UsersDisable(string) error {
-	return errors.New("users:disable")
-}
-
-func TestUsers(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"users:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"users"},
-			expected: "users:list",
-		},
-		{
-			args:     []string{"users:enable", "test"},
-			expected: "",
-		},
-		{
-			args:     []string{"users:disable", "test"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Users(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/utils.go b/parser/utils.go
deleted file mode 100644
index f1389f1d7..000000000
--- a/parser/utils.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package parser
-
-import (
-	"fmt"
-	"log"
-	"strconv"
-
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-func safeGetString(args map[string]interface{}, key string) string {
-	return safeGetValue(args, key, "")
-}
-
-func safeGetBool(args map[string]interface{}, key string) bool {
-	return safeGetValue(args, key, false)
-}
-
-func safeGetInt(args map[string]interface{}, key string) int {
-	if args[key] == nil {
-		return 0
-	}
-	retVal, err := strconv.Atoi(args[key].(string))
-	if err != nil {
-		log.Fatalf("could not convert %s to int: %v", args[key], err)
-	}
-	return retVal
-}
-
-func safeGetValue[T any](args map[string]interface{}, key string, defaultValue T) T {
-	if args[key] == nil {
-		return defaultValue
-	}
-	return args[key].(T)
-}
-
-func responseLimit(limit string) (int, error) {
-	if limit == "" {
-		return -1, nil
-	}
-
-	return strconv.Atoi(limit)
-}
-
-// PrintUsage runs if no matching command is found.
-func PrintUsage(cmdr cmd.Commander) {
-	cmdr.PrintErrln("Found no matching command, try 'drycc help'")
-	cmdr.PrintErrln("Usage: drycc <command> [<args>...]")
-}
-
-func printHelp(argv []string, usage string) bool {
-	if len(argv) > 1 {
-		if argv[1] == "--help" || argv[1] == "-h" {
-			fmt.Print(usage)
-			return true
-		}
-	}
-
-	return false
-}
diff --git a/parser/utils_test.go b/parser/utils_test.go
deleted file mode 100644
index dcdf29dd3..000000000
--- a/parser/utils_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package parser
-
-import "testing"
-
-func TestSafeGet(t *testing.T) {
-	t.Parallel()
-
-	expected := "foo"
-
-	test := make(map[string]interface{}, 1)
-	test["test"] = "foo"
-
-	actual := safeGetString(test, "test")
-
-	if expected != actual {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-}
-
-func TestSafeGetNil(t *testing.T) {
-	t.Parallel()
-
-	expected := ""
-
-	test := make(map[string]interface{}, 1)
-	test["test"] = nil
-
-	actual := safeGetString(test, "test")
-
-	if expected != actual {
-		t.Errorf("Expected %s, Got %s", expected, actual)
-	}
-}
-
-func TestSafeGetInt(t *testing.T) {
-	t.Parallel()
-
-	expected := 1
-
-	test := make(map[string]interface{}, 1)
-	test["test"] = "1"
-
-	actual := safeGetInt(test, "test")
-
-	if expected != actual {
-		t.Errorf("Expected %d, Got %d", expected, actual)
-	}
-
-	if actual = safeGetInt(test, "foo"); actual != 0 {
-		t.Errorf("Expected 0, Got %d", actual)
-	}
-}
-
-func TestPrintHelp(t *testing.T) {
-	t.Parallel()
-
-	usage := ""
-
-	if !printHelp([]string{"ps", "--help"}, usage) {
-		t.Error("Expected true")
-	}
-
-	if !printHelp([]string{"ps", "-h"}, usage) {
-		t.Error("Expected true")
-	}
-
-	if printHelp([]string{"ps"}, usage) {
-		t.Error("Expected false")
-	}
-
-	if printHelp([]string{"ps", "--foo"}, usage) {
-		t.Error("Expected false")
-	}
-}
diff --git a/parser/version.go b/parser/version.go
deleted file mode 100644
index 69db0a091..000000000
--- a/parser/version.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Version displays the client version
-func Version(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Displays the client version.
-
-Usage: drycc version [options]
-
-Options:
-  -a --all
-    list api and controller versions.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-	if err != nil {
-		return err
-	}
-
-	return cmdr.Version(args["--all"].(bool))
-}
diff --git a/parser/version_test.go b/parser/version_test.go
deleted file mode 100644
index 92b43e458..000000000
--- a/parser/version_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) Version(bool) error {
-	return errors.New("version")
-}
-
-func TestVersion(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"version"},
-			expected: "",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Version(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/parser/volumes.go b/parser/volumes.go
deleted file mode 100644
index fa8b2d409..000000000
--- a/parser/volumes.go
+++ /dev/null
@@ -1,309 +0,0 @@
-package parser
-
-import (
-	docopt "github.com/docopt/docopt-go"
-	"github.com/drycc/workflow-cli/cmd"
-)
-
-// Volumes commands to their specific function.
-func Volumes(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Valid commands for volumes:
-
-volumes:add              create a volume for the application
-volumes:expand           expand a volume for the application
-volumes:list             list volumes in the application
-volumes:info             print information about a volume
-volumes:remove           delete a volume from the application
-volumes:client           the client used to manage volume files
-volumes:mount            mount a volume to process of the application
-volumes:unmount          unmount a volume from process of the application
-
-Use 'drycc help [command]' to learn more.
-`
-
-	switch argv[0] {
-	case "volumes:add":
-		return volumesCreate(argv, cmdr)
-	case "volumes:expand":
-		return volumesExpand(argv, cmdr)
-	case "volumes:list":
-		return volumesList(argv, cmdr)
-	case "volumes:info":
-		return volumesInfo(argv, cmdr)
-	case "volumes:remove":
-		return volumesDelete(argv, cmdr)
-	case "volumes:client":
-		return volumesClient(argv, cmdr)
-	case "volumes:mount":
-		return volumesMount(argv, cmdr)
-	case "volumes:unmount":
-		return volumesUnmount(argv, cmdr)
-	default:
-		if printHelp(argv, usage) {
-			return nil
-		}
-
-		if argv[0] == "volumes" {
-			argv[0] = "volumes:list"
-			return volumesList(argv, cmdr)
-		}
-
-		PrintUsage(cmdr)
-		return nil
-	}
-}
-
-func volumesCreate(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Create a volume for the application.
-
-Usage: drycc volumes:add <name> <size> [options]
-
-Arguments:
-  <name>
-    the volume name.
-  <size>
-    the volume size, such as '500G'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -t --type=<type>
-    the volume type, such as csi, nfs, default is 'csi'.
-  --nfs-server=<nfs-server>
-    the hostname or ip address of the nfs server.
-  --nfs-path=<nfs-path>
-    path that is exported by the nfs server.
-  --nfs-read-only
-    a flag indicating whether the storage will be mounted as read only.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	vType := safeGetValue(args, "--type", "csi")
-	name := safeGetString(args, "<name>")
-	size := safeGetString(args, "<size>")
-	parameters := map[string]interface{}{}
-	if vType == "nfs" {
-		server := safeGetString(args, "--nfs-server")
-		path := safeGetString(args, "--nfs-path")
-		readOnly := safeGetBool(args, "--nfs-read-only")
-		parameters["nfs"] = map[string]interface{}{
-			"server":   server,
-			"path":     path,
-			"readOnly": readOnly,
-		}
-	}
-
-	return cmdr.VolumesCreate(app, name, vType, size, parameters)
-}
-
-func volumesExpand(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Expand a volume for the application.
-
-Usage: drycc volumes:expand <name> <size> [options]
-
-Arguments:
-  <name>
-    the volume name.
-  <size>
-    the volume size, such as '500G'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-	size := safeGetString(args, "<size>")
-
-	return cmdr.VolumesExpand(app, name, size)
-}
-
-func volumesList(argv []string, cmdr cmd.Commander) error {
-	usage := `
-List volumes in the application.
-
-Usage: drycc volumes:list [options]
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-  -l --limit=<num>
-    the maximum number of results to display, defaults to config setting.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	results, err := responseLimit(safeGetString(args, "--limit"))
-
-	if err != nil {
-		return err
-	}
-	app := safeGetString(args, "--app")
-
-	return cmdr.VolumesList(app, results)
-}
-
-func volumesInfo(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Print information about a volume.
-
-Usage: drycc volumes:info <name> [options]
-
-Arguments:
-  <name>
-    the volume name to be info.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.VolumesInfo(app, name)
-}
-
-func volumesDelete(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Delete a volume from the application.
-
-Usage: drycc volumes:remove <name> [options]
-
-Arguments:
-  <name>
-    the volume name to be removed.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.VolumesDelete(app, name)
-}
-
-func volumesClient(argv []string, cmdr cmd.Commander) error {
-	usage := `
-The client used to manage volume files.
-
-Usage: drycc volumes:client <cmd> <args>... [options]
-
-Arguments:
-  <cmd>
-    ls         list volume files
-    cp         copy volume files
-    rm         remove volume files
-  <args>
-    arguments for running commands, when cmd is 'cp', args should be '[source] [dest]'.
-    volume path format 'vol://volumename/', '/' is equivalent to the mount path.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	cmd := safeGetString(args, "<cmd>")
-	arguments := safeGetValue(args, "<args>", []string{})
-	return cmdr.VolumesClient(app, cmd, arguments...)
-}
-
-func volumesMount(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Mount a volume for an application.
-
-Usage: drycc volumes:mount <name> <ptype>=<path>... [options]
-
-Arguments:
-  <name>
-    the volume name.
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'worker'.
-  <path>
-    the filesystem path.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.VolumesMount(app, name, args["<ptype>=<path>"].([]string))
-}
-
-func volumesUnmount(argv []string, cmdr cmd.Commander) error {
-	usage := `
-Unmount a volume for an application.
-
-Usage: drycc volumes:unmount <name> <ptype>... [options]
-
-Arguments:
-  <name>
-    the volume name.
-  <ptype>
-    the process name as defined in your Procfile, such as 'web' or 'worker'.
-
-Options:
-  -a --app=<app>
-    the uniquely identifiable name for the application.
-`
-	args, err := docopt.ParseArgs(usage, argv, "")
-
-	if err != nil {
-		return err
-	}
-
-	app := safeGetString(args, "--app")
-	name := safeGetString(args, "<name>")
-
-	return cmdr.VolumesUnmount(app, name, args["<ptype>"].([]string))
-}
diff --git a/parser/volumes_test.go b/parser/volumes_test.go
deleted file mode 100644
index 52d9cf1f2..000000000
--- a/parser/volumes_test.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package parser
-
-import (
-	"bytes"
-	"errors"
-	"testing"
-
-	"github.com/drycc/workflow-cli/pkg/testutil"
-	"github.com/stretchr/testify/assert"
-)
-
-// Create fake implementations of each method that return the argument
-// we expect to have called the function (as an error to satisfy the interface).
-
-func (d FakeDryccCmd) VolumesList(string, int) error {
-	return errors.New("volumes:list")
-}
-
-func (d FakeDryccCmd) VolumesInfo(string, string) error {
-	return errors.New("volumes:info")
-}
-
-func (d FakeDryccCmd) VolumesCreate(string, string, string, string, map[string]interface{}) error {
-	return errors.New("volumes:add")
-}
-
-func (d FakeDryccCmd) VolumesExpand(string, string, string) error {
-	return errors.New("volumes:expand")
-}
-
-func (d FakeDryccCmd) VolumesDelete(string, string) error {
-	return errors.New("volumes:remove")
-}
-
-func (d FakeDryccCmd) VolumesClient(string, string, ...string) error {
-	return errors.New("volumes:client")
-}
-
-func (d FakeDryccCmd) VolumesMount(string, string, []string) error {
-	return errors.New("volumes:mount")
-}
-
-func (d FakeDryccCmd) VolumesUnmount(string, string, []string) error {
-	return errors.New("volumes:unmount")
-}
-
-func TestVolumes(t *testing.T) {
-	t.Parallel()
-
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := FakeDryccCmd{WOut: &b, ConfigFile: cf}
-
-	// cases defines the arguments and expected return of the call.
-	// if expected is "", it defaults to args[0].
-	cases := []struct {
-		args     []string
-		expected string
-	}{
-		{
-			args:     []string{"volumes:add", "myvolume", "500G"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:expand", "myvolume", "500G"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:list"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:remove", "myvolume"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:client", "ls", "--", "vol://myvolume"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:mount", "myvolume", "cmd=data/cmd1"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes:unmount", "myvolume", "cmd"},
-			expected: "",
-		},
-		{
-			args:     []string{"volumes"},
-			expected: "volumes:list",
-		},
-	}
-
-	// For each case, check that calling the route with the arguments
-	// returns the expected error, which is args[0] if not provided.
-	for _, c := range cases {
-		var expected string
-		if c.expected == "" {
-			expected = c.args[0]
-		} else {
-			expected = c.expected
-		}
-		err = Volumes(c.args, cmdr)
-		assert.Error(t, errors.New(expected), err)
-	}
-}
diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go
new file mode 100644
index 000000000..4dedd9363
--- /dev/null
+++ b/pkg/i18n/i18n.go
@@ -0,0 +1,209 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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 i18n
+
+import (
+	"archive/zip"
+	"bytes"
+	"embed"
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/chai2010/gettext-go"
+
+	"k8s.io/klog/v2"
+)
+
+//go:embed translations
+var translations embed.FS
+
+// defaultLanguage is the default language for translations. It is used when no language is specified in the translation key.
+const defaultLanguage = "en_US"
+
+var knownTranslations = map[string][]string{
+	"drycc": {
+		"en_US",
+		"zh_CN",
+	},
+	// only used for unit tests.
+	"test": {
+		"en_US",
+		"zh_CN",
+	},
+}
+
+var (
+	lazyLoadTranslationsOnce sync.Once
+	LoadTranslationsFunc     = func() error {
+		return LoadTranslations("drycc", nil)
+	}
+	translationsLoaded bool
+)
+
+// SetLoadTranslationsFunc sets the function called to lazy load translations.
+// It must be called in an init() func that occurs BEFORE any i18n.T() calls are made by any package. You can
+// accomplish this by creating a separate package containing your init() func, and then importing that package BEFORE
+// any other packages that call i18n.T().
+//
+// Example Usage:
+//
+//	package myi18n
+//
+//	import "k8s.io/kubectl/pkg/util/i18n"
+//
+//	func init() {
+//		if err := i18n.SetLoadTranslationsFunc(loadCustomTranslations); err != nil {
+//			panic(err)
+//		}
+//	}
+//
+//	func loadCustomTranslations() error {
+//		// Load your custom translations here...
+//	}
+//
+// And then in your main or root command package, import your custom package like this:
+//
+//	import (
+//		// Other imports that don't need i18n...
+//		_ "example.com/myapp/myi18n"
+//		// Other imports that do need i18n...
+//	)
+func SetLoadTranslationsFunc(f func() error) error {
+	if translationsLoaded {
+		return errors.New("translations have already been loaded")
+	}
+	LoadTranslationsFunc = func() error {
+		if err := f(); err != nil {
+			return err
+		}
+		translationsLoaded = true
+		return nil
+	}
+	return nil
+}
+
+func loadSystemLanguage() string {
+	// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
+	// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
+	langStr := os.Getenv("LC_ALL")
+	if langStr == "" {
+		langStr = os.Getenv("LC_MESSAGES")
+	}
+	if langStr == "" {
+		langStr = os.Getenv("LANG")
+	}
+
+	if langStr == "" {
+		klog.V(3).Infof("Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to %s", defaultLanguage)
+		return defaultLanguage
+	}
+	pieces := strings.Split(langStr, ".")
+	if len(pieces) != 2 {
+		klog.V(3).Infof("Unexpected system language (%s), defaulting to %s", langStr, defaultLanguage)
+		return defaultLanguage
+	}
+	return pieces[0]
+}
+
+func findLanguage(root string, getLanguageFn func() string) string {
+	langStr := getLanguageFn()
+
+	translations := knownTranslations[root]
+	for ix := range translations {
+		if translations[ix] == langStr {
+			return langStr
+		}
+	}
+	klog.V(3).Infof("Couldn't find translations for %s, using %s", langStr, defaultLanguage)
+	return defaultLanguage
+}
+
+// LoadTranslations loads translation files. getLanguageFn should return a language
+// string (e.g. 'en-US'). If getLanguageFn is nil, then the loadSystemLanguage function
+// is used, which uses the 'LANG' environment variable.
+func LoadTranslations(root string, getLanguageFn func() string) error {
+	if getLanguageFn == nil {
+		getLanguageFn = loadSystemLanguage
+	}
+
+	langStr := findLanguage(root, getLanguageFn)
+	translationFiles := []string{
+		fmt.Sprintf("%s/%s/LC_MESSAGES/cli.po", root, langStr),
+		fmt.Sprintf("%s/%s/LC_MESSAGES/cli.mo", root, langStr),
+	}
+	klog.V(3).Infof("Setting language to %s", langStr)
+	// TODO: list the directory and load all files.
+	buf := new(bytes.Buffer)
+	w := zip.NewWriter(buf)
+
+	// Make sure to check the error on Close.
+	for _, file := range translationFiles {
+		filename := "translations/" + file
+		f, err := w.Create(file)
+		if err != nil {
+			return err
+		}
+		data, err := translations.ReadFile(filename)
+		if err != nil {
+			return err
+		}
+		if _, err := f.Write(data); err != nil {
+			return nil
+		}
+	}
+	if err := w.Close(); err != nil {
+		return err
+	}
+	gettext.BindLocale(gettext.New("cli", root+".zip", buf.Bytes()))
+	gettext.SetDomain("cli")
+	gettext.SetLanguage(langStr)
+	translationsLoaded = true
+	return nil
+}
+
+func lazyLoadTranslations() {
+	lazyLoadTranslationsOnce.Do(func() {
+		if translationsLoaded {
+			return
+		}
+		if err := LoadTranslationsFunc(); err != nil {
+			klog.Warning("Failed to load translations")
+		}
+	})
+}
+
+// T translates a string, possibly substituting arguments into it along
+// the way. If len(args) is > 0, args1 is assumed to be the plural value
+// and plural translation is used.
+func T(defaultValue string, args ...int) string {
+	lazyLoadTranslations()
+	if len(args) == 0 {
+		return gettext.PGettext("", defaultValue)
+	}
+	return fmt.Sprintf(gettext.PNGettext("", defaultValue, defaultValue+".plural", args[0]),
+		args[0])
+}
+
+// Errorf produces an error with a translated error string.
+// Substitution is performed via the `T` function above, following
+// the same rules.
+func Errorf(defaultValue string, args ...int) error {
+	return errors.New(T(defaultValue, args...))
+}
diff --git a/pkg/i18n/i18n_test.go b/pkg/i18n/i18n_test.go
new file mode 100644
index 000000000..33c6c9a48
--- /dev/null
+++ b/pkg/i18n/i18n_test.go
@@ -0,0 +1,292 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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 i18n
+
+import (
+	"os"
+	"sync"
+	"testing"
+
+	"github.com/chai2010/gettext-go"
+)
+
+var knownTestLocale = "zh_CN.UTF-8"
+
+func TestTranslation(t *testing.T) {
+	err := LoadTranslations("test", func() string { return defaultLanguage })
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	result := T("test_string")
+	if result != "foo" {
+		t.Errorf("expected: %s, saw: %s", "foo", result)
+	}
+}
+
+func TestTranslationPlural(t *testing.T) {
+	err := LoadTranslations("test", func() string { return defaultLanguage })
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	result := T("test_plural", 3)
+	if result != "there were 3 items" {
+		t.Errorf("expected: %s, saw: %s", "there were 3 items", result)
+	}
+
+	result = T("test_plural", 1)
+	if result != "there was 1 item" {
+		t.Errorf("expected: %s, saw: %s", "there was 1 item", result)
+	}
+}
+
+func TestTranslationUsingEnvVar(t *testing.T) {
+	// We must backup and restore env vars before setting test values in tests
+	// othervise we are risking to break other tests/test cases
+	// which rely on the same env vars
+	envVarsToBackup := []string{"LC_MESSAGES", "LANG", "LC_ALL"}
+	expectedStrEnUSLocale := "baz"
+	expectedStrFallback := "foo"
+
+	testCases := []struct {
+		name        string
+		setenv      map[string]string
+		expectedStr string
+	}{
+		{
+			name:        "Only LC_ALL is set",
+			setenv:      map[string]string{"LC_ALL": knownTestLocale},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name:        "Only LC_MESSAGES is set",
+			setenv:      map[string]string{"LC_MESSAGES": knownTestLocale},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name:        "Only LANG",
+			setenv:      map[string]string{"LANG": knownTestLocale},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name: "LC_MESSAGES overrides LANG",
+			setenv: map[string]string{
+				"LANG":        "be_BY.UTF-8", // Unknown locale
+				"LC_MESSAGES": knownTestLocale,
+			},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name: "LC_ALL overrides LANG",
+			setenv: map[string]string{
+				"LANG":   "be_BY.UTF-8", // Unknown locale
+				"LC_ALL": knownTestLocale,
+			},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name: "LC_ALL overrides LC_MESSAGES",
+			setenv: map[string]string{
+				"LC_MESSAGES": "be_BY.UTF-8", // Unknown locale
+				"LC_ALL":      knownTestLocale,
+			},
+			expectedStr: expectedStrEnUSLocale,
+		},
+		{
+			name:        "Unknown locale in LANG",
+			setenv:      map[string]string{"LANG": "be_BY.UTF-8"},
+			expectedStr: expectedStrFallback,
+		},
+		{
+			name:        "Unknown locale in LC_MESSAGES",
+			setenv:      map[string]string{"LC_MESSAGES": "be_BY.UTF-8"},
+			expectedStr: expectedStrFallback,
+		},
+		{
+			name:        "Unknown locale in LC_ALL",
+			setenv:      map[string]string{"LC_ALL": "be_BY.UTF-8"},
+			expectedStr: expectedStrFallback,
+		},
+		{
+			name:        "Invalid env var",
+			setenv:      map[string]string{"LC_MESSAGES": "fake.locale.UTF-8"},
+			expectedStr: expectedStrFallback,
+		},
+		{
+			name:        "No env vars",
+			expectedStr: expectedStrFallback,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			for _, envVar := range envVarsToBackup {
+				if envVarValue := os.Getenv(envVar); envVarValue != "" {
+					envVarValue, envVar := envVarValue, envVar
+					t.Cleanup(func() { os.Setenv(envVar, envVarValue) })
+					os.Unsetenv(envVar)
+				}
+			}
+
+			for envVar, envVarValue := range test.setenv {
+				t.Setenv(envVar, envVarValue)
+			}
+
+			err := LoadTranslations("test", nil)
+			if err != nil {
+				t.Errorf("Unexpected error: %v", err)
+			}
+
+			result := T("test_string")
+			if result != test.expectedStr {
+				t.Errorf("expected: %s, saw: %s", test.expectedStr, result)
+			}
+		})
+	}
+}
+
+// resetLazyLoading allows multiple tests to test translation lazy loading by resetting the state
+func resetLazyLoading() {
+	translationsLoaded = false
+	lazyLoadTranslationsOnce = sync.Once{}
+}
+
+func TestLazyLoadTranslationFuncIsCalled(t *testing.T) {
+	resetLazyLoading()
+
+	timesCalled := 0
+	err := SetLoadTranslationsFunc(func() error {
+		timesCalled++
+		return LoadTranslations("test", func() string { return "zh_CN" })
+	})
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if translationsLoaded {
+		t.Errorf("expected translationsLoaded to be false, but it was true")
+	}
+
+	// Translation should succeed and use the lazy loaded translations
+	result := T("test_string")
+	if result != "baz" {
+		t.Errorf("expected: %s, saw: %s", "baz", result)
+	}
+	if timesCalled != 1 {
+		t.Errorf("expected LoadTranslationsFunc to have been called 1 time, but it was called %d times", timesCalled)
+	}
+	if !translationsLoaded {
+		t.Errorf("expected translationsLoaded to be true, but it was false")
+	}
+
+	// Call T() again, and timesCalled should remain 1
+	T("test_string")
+	if timesCalled != 1 {
+		t.Errorf("expected LoadTranslationsFunc to have been called 1 time, but it was called %d times", timesCalled)
+	}
+}
+
+func TestLazyLoadTranslationFuncOnlyCalledIfTranslationsNotLoaded(t *testing.T) {
+	resetLazyLoading()
+
+	// Set a custom translations func
+	timesCalled := 0
+	err := SetLoadTranslationsFunc(func() error {
+		timesCalled++
+		return LoadTranslations("test", func() string { return defaultLanguage })
+	})
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if translationsLoaded {
+		t.Errorf("expected translationsLoaded to be false, but it was true")
+	}
+
+	// Explicitly load translations before lazy loading can occur
+	err = LoadTranslations("test", func() string { return defaultLanguage })
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if !translationsLoaded {
+		t.Errorf("expected translationsLoaded to be true, but it was false")
+	}
+
+	// Translation should succeed, and use the explicitly loaded translations, not the lazy loaded ones
+	result := T("test_string")
+	if result != "foo" {
+		t.Errorf("expected: %s, saw: %s", "foo", result)
+	}
+	if timesCalled != 0 {
+		t.Errorf("expected LoadTranslationsFunc to have not been called, but it was called %d times", timesCalled)
+	}
+}
+
+func TestSetCustomLoadTranslationsFunc(t *testing.T) {
+	resetLazyLoading()
+
+	// Set a custom translations func that loads translations from a directory
+	err := SetLoadTranslationsFunc(func() error {
+		gettext.BindLocale(gettext.New("cli", "./translations/test"))
+		gettext.SetDomain("cli")
+		gettext.SetLanguage("zh_CN")
+		return nil
+	})
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if translationsLoaded {
+		t.Errorf("expected translationsLoaded to be false, but it was true")
+	}
+
+	// Translation should succeed
+	result := T("test_string")
+	if result != "baz" {
+		t.Errorf("expected: %s, saw: %s", "baz", result)
+	}
+	if !translationsLoaded {
+		t.Errorf("expected translationsLoaded to be true, but it was false")
+	}
+}
+
+func TestSetCustomLoadTranslationsFuncAfterTranslationsLoadedShouldFail(t *testing.T) {
+	resetLazyLoading()
+
+	// Explicitly load translations
+	err := LoadTranslations("test", func() string { return defaultLanguage })
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if !translationsLoaded {
+		t.Errorf("expected translationsLoaded to be true, but it was false")
+	}
+
+	// This should fail because translations have already been loaded, and the custom function should not be called.
+	timesCalled := 0
+	err = SetLoadTranslationsFunc(func() error {
+		timesCalled++
+		return nil
+	})
+	if err == nil {
+		t.Errorf("expected error, but it did not occur")
+	}
+	if timesCalled != 0 {
+		t.Errorf("expected LoadTranslationsFunc to have not been called, but it was called %d times", timesCalled)
+	}
+}
diff --git a/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.mo b/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.mo
new file mode 100644
index 0000000000000000000000000000000000000000..953686e9c432bc379b801f0c3273c0f3e03c5843
GIT binary patch
diff --git a/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po b/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
new file mode 100644
index 000000000..3ec7a5ef5
--- /dev/null
+++ b/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
@@ -0,0 +1,1727 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2025-03-31 14:12+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: internal/parser/builds.go:58 internal/parser/builds.go:105
+msgid "A YAML file used to supply a Procfile to the application"
+msgstr ""
+
+#: internal/parser/keys.go:52
+msgid "A local file path to an SSH public key used to push application code"
+msgstr ""
+
+#: internal/parser/config.go:158 internal/parser/config.go:195
+msgid "A path leading to an environment file"
+msgstr ""
+
+#: internal/parser/tls.go:121
+msgid "ACME account email"
+msgstr ""
+
+#: internal/parser/tls.go:122
+msgid "ACME server URL"
+msgstr ""
+
+#: internal/parser/volumes.go:90
+msgid "Access key id for authentication"
+msgstr ""
+
+#: internal/parser/tls.go:55 internal/parser/tls.go:83
+msgid "Action to perform, 'enable' or 'disable'"
+msgstr ""
+
+#: internal/parser/tokens.go:58
+msgid "Add a token for controller authentication"
+msgstr ""
+
+#: internal/parser/perms.go:69
+msgid "Add a user permissions"
+msgstr ""
+
+#: internal/parser/keys.go:56
+msgid "Add an SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:65
+msgid "Add an SSL certificate to an app"
+msgstr ""
+
+#: internal/parser/labels.go:59
+msgid "Add new application's label"
+msgstr ""
+
+#: internal/parser/keys.go:57
+msgid "Adds SSH keys for the logged in user"
+msgstr ""
+
+#: internal/parser/git.go:35
+msgid "Adds git remote of application to repository"
+msgstr ""
+
+#: internal/parser/config.go:160
+msgid "Allows you to have the pull overwrite keys to the path"
+msgstr ""
+
+#: internal/parser/config.go:57
+msgid "An app config info"
+msgstr ""
+
+#: internal/parser/tls.go:23
+msgid "Application name"
+msgstr ""
+
+#: internal/parser/certs.go:136
+msgid "Attach a certificate to a domain"
+msgstr ""
+
+#: internal/parser/certs.go:135
+msgid "Attach an SSL certificate to a domain"
+msgstr ""
+
+#: internal/parser/routes.go:179
+msgid "Attach to gateway"
+msgstr ""
+
+#: internal/parser/auth.go:34
+msgid "Authenticate against a controller"
+msgstr ""
+
+#: internal/parser/tls.go:88
+msgid "Automatic certificate management"
+msgstr ""
+
+#: internal/parser/domains.go:55
+msgid "Bind a domain to an application"
+msgstr ""
+
+#: internal/parser/resources.go:245
+msgid "Bind a resource for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:89
+msgid "Bucket name in object storage"
+msgstr ""
+
+#: internal/parser/tls.go:123
+msgid "CA key ID"
+msgstr ""
+
+#: internal/parser/tls.go:124
+msgid "CA key secret"
+msgstr ""
+
+#: internal/parser/pts.go:156
+msgid "Clean process types of not used"
+msgstr ""
+
+#: internal/parser/auth.go:53
+msgid "Clear the current user session"
+msgstr ""
+
+#: internal/parser/apps.go:172
+msgid "Command execution timeout in seconds"
+msgstr ""
+
+#: internal/parser/certs.go:131
+msgid ""
+"Common name of the domain to attach to (needs to already be in the system)"
+msgstr ""
+
+#: internal/parser/certs.go:156
+msgid "Common name of the domain to detach from"
+msgstr ""
+
+#: internal/parser/tls.go:115
+msgid "Configure automatic certificate management issuer details"
+msgstr ""
+
+#: internal/parser/apps.go:44
+msgid "Create a new application"
+msgstr ""
+
+#: internal/parser/resources.go:101
+msgid "Create a resource for the application"
+msgstr ""
+
+#: internal/parser/routes.go:56
+msgid "Create a route for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:61
+msgid "Create a volume for the application"
+msgstr ""
+
+#: internal/parser/gateways.go:59
+msgid "Create gateways for an application"
+msgstr ""
+
+#: internal/parser/services.go:55
+msgid "Create service for an application"
+msgstr ""
+
+#: internal/parser/apps.go:45
+msgid ""
+"Creates a new application. If no <id> is provided, one will be generated "
+"automatically"
+msgstr ""
+
+#: internal/parser/builds.go:45
+msgid ""
+"Creates a new build of an application. Imports an <image> and deploys it to "
+"Drycc\n"
+"as a new release. If a Procfile or drycc.yaml is present in the current "
+"directory,\n"
+"it will be used as the default for this application."
+msgstr ""
+
+#: internal/parser/services.go:56
+msgid ""
+"Creates extra service for an application and binds it to specific route of "
+"the main app domain"
+msgstr ""
+
+#: internal/parser/gateways.go:60
+msgid ""
+"Creates gateways for an application and binds it to allow listener of the "
+"main app domain"
+msgstr ""
+
+#: internal/parser/routes.go:57
+msgid "Creates routes for an application, provides a way to route requests"
+msgstr ""
+
+#: internal/parser/resources.go:220
+msgid "Delete a resource from the application"
+msgstr ""
+
+#: internal/parser/volumes.go:184
+msgid "Delete a volume from the application"
+msgstr ""
+
+#: internal/parser/ps.go:155
+msgid "Delete the selected processes"
+msgstr ""
+
+#: internal/parser/gateways.go:88
+msgid "Deletes specific gateway for application"
+msgstr ""
+
+#: internal/parser/releases.go:109
+msgid "Deploy the latest release by process types"
+msgstr ""
+
+#: internal/parser/pts.go:64
+msgid "Describe a process type"
+msgstr ""
+
+#: internal/parser/apps.go:190
+msgid "Destroy an application"
+msgstr ""
+
+#: internal/parser/certs.go:160
+msgid "Detach an SSL certificate from a domain"
+msgstr ""
+
+#: internal/parser/certs.go:161
+msgid "Detach certificate from a domain"
+msgstr ""
+
+#: internal/parser/routes.go:211
+msgid "Dettach to gateway"
+msgstr ""
+
+#: internal/parser/users.go:81
+msgid "Disable a user"
+msgstr ""
+
+#: internal/parser/users.go:82
+msgid ""
+"Disable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+
+#: internal/parser/autorollback.go:58
+msgid "Disable autorollback for an application"
+msgstr ""
+
+#: internal/parser/routing.go:57
+msgid "Disable routing for an application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:59
+msgid "Disables autodeploy for an app"
+msgstr ""
+
+#: cmd/root.go:34 internal/parser/version.go:16
+msgid "Display client version"
+msgstr ""
+
+#: cmd/root.go:35
+msgid "Display help information"
+msgstr "Display help information"
+
+#: internal/parser/auth.go:71
+msgid "Display the current user"
+msgstr ""
+
+#: internal/parser/version.go:17
+msgid "Displays the client version"
+msgstr ""
+
+#: internal/parser/auth.go:72
+msgid "Displays the currently logged in user"
+msgstr ""
+
+#: internal/parser/apps.go:56
+msgid "Do not create a 'drycc' git remote"
+msgstr ""
+
+#: internal/parser/builds.go:59 internal/parser/builds.go:106
+msgid "Drycc config path to the application"
+msgstr ""
+
+#: internal/parser/users.go:56
+msgid "Enable a user"
+msgstr ""
+
+#: internal/parser/users.go:57
+msgid ""
+"Enable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+
+#: internal/parser/autorollback.go:46
+msgid "Enable autorollback for an application"
+msgstr ""
+
+#: internal/parser/routing.go:45
+msgid "Enable routing for an application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:47
+msgid "Enables autodeploy for an app"
+msgstr ""
+
+#: internal/parser/auth.go:43
+msgid "Enables or disables SSL certificate verification for API requests"
+msgstr ""
+
+#: internal/parser/volumes.go:88
+msgid "Endpoint url for object storage service"
+msgstr ""
+
+#: internal/parser/ps.go:102
+msgid "Execute a command in a container"
+msgstr ""
+
+#: internal/parser/volumes.go:116
+msgid "Expand a volume capacity for the application"
+msgstr ""
+
+#: internal/parser/config.go:145
+msgid ""
+"Extract all environment variables from an application or config group. for "
+"local use.\n"
+"\n"
+"The environmental variables can be piped into a file, 'drycc config:pull > "
+"file',\n"
+"or stored locally in a file named .env. This file can be\n"
+"read by foreman to load the local environment for your app."
+msgstr ""
+
+#: internal/parser/auth.go:78
+msgid "Fetch detailed user information"
+msgstr ""
+
+#: internal/parser/certs.go:112
+msgid "Fetch more detailed information about a certificate"
+msgstr ""
+
+#: internal/parser/tls.go:60
+msgid "Force TLS settings to HTTPS-only redirection"
+msgstr ""
+
+#: internal/parser/releases.go:117
+msgid "Force deploy"
+msgstr ""
+
+#: internal/parser/resources.go:151
+msgid "Get a resource's detail in the application"
+msgstr ""
+
+#: internal/parser/certs.go:111
+msgid "Get detailed informaton about the certificate"
+msgstr ""
+
+#: internal/parser/routes.go:123
+msgid "Get route rules"
+msgstr ""
+
+#: internal/parser/routes.go:124
+msgid "Get route rules for an application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:184
+msgid "How often (in seconds) to perform the probe"
+msgstr ""
+
+#: internal/parser/keys.go:29
+msgid "List SSH keys for the logged in user"
+msgstr ""
+
+#: internal/parser/certs.go:40
+msgid "List SSL certificates for an application"
+msgstr ""
+
+#: internal/parser/apps.go:65
+msgid "List accessible applications"
+msgstr ""
+
+#: internal/parser/limits.go:140
+msgid "List all available limit plans"
+msgstr ""
+
+#: internal/parser/limits.go:118
+msgid "List all available limit specs"
+msgstr ""
+
+#: internal/parser/resources.go:67
+msgid "List all available plans for a resource service"
+msgstr ""
+
+#: internal/parser/resources.go:44
+msgid "List all available resource services"
+msgstr ""
+
+#: internal/parser/users.go:32
+msgid "List all registered users"
+msgstr ""
+
+#: internal/parser/perms.go:37
+msgid "List all user permissions"
+msgstr ""
+
+#: internal/parser/releases.go:53
+msgid "List an application's release history"
+msgstr ""
+
+#: internal/parser/gateways.go:36
+msgid "List application gateways"
+msgstr ""
+
+#: internal/parser/ps.go:37
+msgid "List application pods"
+msgstr ""
+
+#: internal/parser/pts.go:37
+msgid "List application process types"
+msgstr ""
+
+#: internal/parser/routes.go:100
+msgid "List application routes"
+msgstr ""
+
+#: internal/parser/services.go:76
+msgid "List application services"
+msgstr ""
+
+#: internal/parser/labels.go:33
+msgid "List application's labels"
+msgstr ""
+
+#: internal/parser/autoscale.go:34
+msgid "List autoscale options for an application"
+msgstr ""
+
+#: internal/parser/domains.go:36
+msgid "List domains bound to an application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:50
+msgid "List healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:43
+msgid "List registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:40
+msgid "List resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:38
+msgid "List resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/resources.go:123
+msgid "List resources in the application"
+msgstr ""
+
+#: internal/parser/limits.go:117
+msgid "List specification information of the server"
+msgstr ""
+
+#: internal/parser/tags.go:52
+msgid "List tags for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:132
+msgid "List volumes in the application"
+msgstr ""
+
+#: internal/parser/apps.go:66
+msgid "Lists applications visible to the current user"
+msgstr ""
+
+#: internal/parser/gateways.go:37
+msgid "Lists gateways for an application"
+msgstr ""
+
+#: internal/parser/pts.go:38
+msgid "Lists process types servicing an application"
+msgstr ""
+
+#: internal/parser/ps.go:38
+msgid "Lists processes servicing an application"
+msgstr ""
+
+#: internal/parser/registry.go:44
+msgid "Lists registry information for an application"
+msgstr ""
+
+#: internal/parser/releases.go:54
+msgid "Lists release history for an application"
+msgstr ""
+
+#: internal/parser/tokens.go:33
+msgid "Lists tokens visible to the current controller"
+msgstr ""
+
+#: internal/parser/auth.go:35
+msgid "Logs in by authenticating against a controller"
+msgstr ""
+
+#: internal/parser/auth.go:54
+msgid "Logs out from a controller and clears the user session"
+msgstr ""
+
+#: internal/parser/certs.go:15
+msgid "Manage SSL endpoints for an app"
+msgstr ""
+
+#: internal/parser/tls.go:17
+msgid "Manage TLS/SSL settings for applications"
+msgstr ""
+
+#: internal/parser/domains.go:14
+msgid "Manage and assign domain names to your applications"
+msgstr ""
+
+#: internal/parser/healthchecks.go:23
+msgid "Manage application healthchecks"
+msgstr ""
+
+#: internal/parser/apps.go:17
+msgid "Manage applications used to provide services"
+msgstr ""
+
+#: internal/parser/auth.go:13
+msgid "Manage authentication"
+msgstr ""
+
+#: internal/parser/autodeploy.go:15
+msgid "Manage autodeploy if or not for applications"
+msgstr ""
+
+#: internal/parser/autorollback.go:14
+msgid "Manage autorollback if or not for application"
+msgstr ""
+
+#: internal/parser/autoscale.go:15
+msgid "Manage autoscale for applications"
+msgstr ""
+
+#: internal/parser/builds.go:13
+msgid "Manage builds created using 'git push'"
+msgstr ""
+
+#: internal/parser/config.go:23
+msgid "Manage environment variables that define app config"
+msgstr ""
+
+#: internal/parser/gateways.go:14
+msgid "Manage gateways for your applications"
+msgstr ""
+
+#: internal/parser/git.go:13
+msgid "Manage git for applications"
+msgstr ""
+
+#: internal/parser/labels.go:14
+msgid "Manage labels of application"
+msgstr ""
+
+#: internal/parser/perms.go:14
+msgid "Manage permissions for applications"
+msgstr ""
+
+#: internal/parser/timeouts.go:15
+msgid "Manage pods termination grace period"
+msgstr ""
+
+#: internal/parser/registry.go:19
+msgid "Manage private registry information for your application"
+msgstr ""
+
+#: internal/parser/pts.go:14
+msgid "Manage process types inside an app"
+msgstr ""
+
+#: internal/parser/ps.go:14
+msgid "Manage processes inside an app"
+msgstr ""
+
+#: internal/parser/users.go:14
+msgid "Manage registered users"
+msgstr ""
+
+#: internal/parser/releases.go:24
+msgid "Manage releases of an application"
+msgstr ""
+
+#: internal/parser/limits.go:14
+msgid "Manage resource limits for your application"
+msgstr ""
+
+#: internal/parser/resources.go:16
+msgid "Manage resources for your applications"
+msgstr ""
+
+#: internal/parser/routing.go:13
+msgid "Manage routability of an application"
+msgstr ""
+
+#: internal/parser/routes.go:19
+msgid "Manage routes for your applications"
+msgstr ""
+
+#: internal/parser/services.go:17
+msgid "Manage services for your applications"
+msgstr ""
+
+#: internal/parser/keys.go:14
+msgid "Manage ssh keys used for 'git push' deployments"
+msgstr ""
+
+#: internal/parser/tags.go:19
+msgid "Manage tags for application containers"
+msgstr ""
+
+#: internal/parser/tokens.go:15
+msgid "Manage user tokens"
+msgstr ""
+
+#: internal/parser/volumes.go:15
+msgid "Manage volumes for your applications"
+msgstr ""
+
+#: internal/parser/autoscale.go:73
+msgid "Max replicas to scale up to"
+msgstr ""
+
+#: internal/parser/apps.go:22 internal/parser/apps.go:72
+msgid "Maximum number of results to display"
+msgstr ""
+
+#: internal/parser/healthchecks.go:186
+msgid ""
+"Minimum consecutive successes for the probe to be considered failed after "
+"having succeeded"
+msgstr ""
+
+#: internal/parser/healthchecks.go:185
+msgid ""
+"Minimum consecutive successes for the probe to be considered successful "
+"after having failed"
+msgstr ""
+
+#: internal/parser/volumes.go:236
+msgid "Mount a volume to process of the application"
+msgstr ""
+
+#: internal/parser/apps.go:55 internal/parser/git.go:41
+msgid "Name of remote to create"
+msgstr ""
+
+#: internal/parser/keys.go:51
+msgid "Name of the SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:130
+msgid "Name of the certificate to attach domain to"
+msgstr ""
+
+#: internal/parser/certs.go:155
+msgid "Name of the certificate to deatch from a domain"
+msgstr ""
+
+#: internal/parser/certs.go:59
+msgid "Name of the certificate to reference it by"
+msgstr ""
+
+#: internal/parser/services.go:52
+msgid ""
+"Number or name of the port to access on the pods targeted by the service"
+msgstr ""
+
+#: internal/parser/apps.go:100
+msgid "Open the application in a browser"
+msgstr ""
+
+#: internal/parser/apps.go:101
+msgid "Opens a URL to the application in the default browser"
+msgstr ""
+
+#: internal/parser/git.go:42
+msgid "Overwrite remote of the given name if it already exists"
+msgstr ""
+
+#: internal/parser/ps.go:114
+msgid "Pass stdin to the container"
+msgstr ""
+
+#: internal/parser/volumes.go:86
+msgid "Path that is exported by the nfs server"
+msgstr ""
+
+#: cmd/root.go:33
+msgid "Path to configuration file."
+msgstr "Path to configuration file."
+
+#: internal/parser/routes.go:188 internal/parser/routes.go:220
+msgid "Port is the network port, the gateway listener expects to receive"
+msgstr ""
+
+#: internal/parser/gateways.go:67
+msgid "Port is the network port, the listener expects to receive"
+msgstr ""
+
+#: internal/parser/gateways.go:96
+msgid "Port is the network port, the listener received"
+msgstr "Port is the network port, the listener received"
+
+#: internal/parser/ps.go:133
+msgid "Print a detailed description of the selected process"
+msgstr ""
+
+#: internal/parser/pts.go:65
+msgid "Print a detailed description of the selected process type"
+msgstr ""
+
+#: internal/parser/builds.go:74
+msgid "Print information about a specific build"
+msgstr ""
+
+#: internal/parser/releases.go:80
+msgid "Print information about a specific release"
+msgstr ""
+
+#: internal/parser/volumes.go:158
+msgid "Print information about a volume"
+msgstr ""
+
+#: internal/parser/builds.go:99
+msgid "Print process info about a specific build"
+msgstr ""
+
+#: internal/parser/ps.go:63
+msgid "Print the logs for a container"
+msgstr ""
+
+#: internal/parser/ps.go:64
+msgid "Print the logs for a container in a pod or specified resource"
+msgstr ""
+
+#: internal/parser/ps.go:78
+msgid ""
+"Print the logs for the previous instance of the container in a pod if it "
+"exists"
+msgstr ""
+
+#: internal/parser/ps.go:77
+msgid "Print the logs of this container"
+msgstr ""
+
+#: internal/parser/autoscale.go:35
+msgid "Prints a list of autoscale options for the application"
+msgstr ""
+
+#: internal/parser/labels.go:34
+msgid "Prints a list of labels of the application"
+msgstr ""
+
+#: internal/parser/releases.go:81
+msgid "Prints info about a particular release"
+msgstr ""
+
+#: internal/parser/apps.go:82
+msgid "Prints info about the current application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:35
+msgid "Prints info about the current application's autodeploy if or not"
+msgstr ""
+
+#: internal/parser/autorollback.go:34
+msgid "Prints info about the current application's autorollback if or not"
+msgstr ""
+
+#: internal/parser/config.go:159
+msgid "Prompts for each value to be overwritten"
+msgstr ""
+
+#: internal/parser/gateways.go:68
+msgid ""
+"Protocol specifies the network protocol this listener expects to receive. "
+"Supports TCP, UDP, TLS, HTTP, and HTTPS"
+msgstr ""
+
+#: internal/parser/gateways.go:97
+msgid "Protocol specifies the network protocol this listener received"
+msgstr "Protocol specifies the network protocol this listener received"
+
+#: internal/parser/tokens.go:55
+msgid "Provide a alias for controller authentication token"
+msgstr ""
+
+#: internal/parser/tokens.go:68 internal/parser/auth.go:42
+msgid "Provide a password for the account"
+msgstr ""
+
+#: internal/parser/tokens.go:67 internal/parser/auth.go:41
+msgid "Provide a username for the account"
+msgstr ""
+
+#: internal/parser/config.go:144
+msgid "Pull environment variables to the path"
+msgstr ""
+
+#: internal/parser/config.go:182
+msgid "Push environment variables from the path"
+msgstr ""
+
+#: internal/parser/limits.go:148
+msgid "Query plans that meet the specified memory capacity, unit GiB"
+msgstr ""
+
+#: internal/parser/limits.go:147
+msgid "Query plans that meet the specified number of cpu cores"
+msgstr ""
+
+#: internal/parser/limits.go:149
+msgid "Query plans that meet the specified spec id, see [specs] subcommand."
+msgstr ""
+
+#: internal/parser/routes.go:240
+msgid "Remove a route from an application"
+msgstr ""
+
+#: internal/parser/tokens.go:89
+msgid "Remove a token for controller authentication"
+msgstr ""
+
+#: internal/parser/perms.go:126
+msgid "Remove a user permissions"
+msgstr ""
+
+#: internal/parser/keys.go:80
+msgid "Remove an SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:89
+msgid "Remove an SSL certificate from an app"
+msgstr ""
+
+#: internal/parser/labels.go:89
+msgid "Remove application's label"
+msgstr ""
+
+#: internal/parser/gateways.go:87
+msgid "Remove gateways from an application"
+msgstr ""
+
+#: internal/parser/services.go:103
+msgid "Remove service from an application"
+msgstr ""
+
+#: internal/parser/keys.go:81
+msgid "Removes an SSH key for the logged in user"
+msgstr ""
+
+#: internal/parser/git.go:51
+msgid "Removes git remote of application from repository"
+msgstr ""
+
+#: internal/parser/pts.go:90
+msgid "Restart an application or process types"
+msgstr ""
+
+#: internal/parser/pts.go:89
+msgid "Restart application process types"
+msgstr ""
+
+#: internal/parser/apps.go:173
+msgid "Retention time of records in seconds"
+msgstr ""
+
+#: internal/parser/apps.go:125
+msgid "Retrieve application log events"
+msgstr ""
+
+#: internal/parser/apps.go:126
+msgid "Retrieves the most recent log events"
+msgstr ""
+
+#: internal/parser/releases.go:134
+msgid "Return to a previous release"
+msgstr ""
+
+#: internal/parser/releases.go:135
+msgid "Rolls back to a previous application release"
+msgstr ""
+
+#: internal/parser/routes.go:158
+msgid "Rules-file is the file name of rule to apply"
+msgstr ""
+
+#: internal/parser/apps.go:162
+msgid "Run a command in an ephemeral app container"
+msgstr ""
+
+#: internal/parser/apps.go:163
+msgid "Runs a command inside an ephemeral app container"
+msgstr ""
+
+#: internal/parser/builds.go:108
+msgid "Save process info to the local"
+msgstr ""
+
+#: internal/parser/pts.go:123
+msgid "Scale process types of replicas"
+msgstr ""
+
+#: internal/parser/pts.go:124
+msgid "Scales an application's processes by type"
+msgstr ""
+
+#: internal/parser/limits.go:125
+msgid ""
+"Search keywords separated by commas, matching must satisfy all of the "
+"specified."
+msgstr ""
+
+#: internal/parser/config.go:223
+msgid "Selects environment groups to attach an app ptype"
+msgstr ""
+
+#: internal/parser/config.go:248
+msgid "Selects environment groups to detach an app ptype"
+msgstr ""
+
+#: internal/parser/autoscale.go:64
+msgid "Set autoscale option per process type for an app"
+msgstr ""
+
+#: internal/parser/config.go:85
+msgid "Set environment variables for an app"
+msgstr ""
+
+#: internal/parser/healthchecks.go:92
+msgid "Set healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:75
+msgid "Set registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:69
+msgid "Set resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:64
+msgid "Set resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/routes.go:149
+msgid "Set route rules"
+msgstr ""
+
+#: internal/parser/routes.go:150
+msgid "Set route rules for an application"
+msgstr ""
+
+#: internal/parser/tags.go:86
+msgid "Set tags for an application"
+msgstr ""
+
+#: internal/parser/config.go:86
+msgid "Sets environment variables for an application or config group"
+msgstr ""
+
+#: internal/parser/config.go:183
+msgid ""
+"Sets environment variables for an application or config group.\n"
+"\n"
+"This file can be read by foreman\n"
+"to load the local environment for your app. The file should be piped via\n"
+"stdin, 'drycc config:push < .env', or using the --path option."
+msgstr ""
+
+#: internal/parser/healthchecks.go:93
+msgid ""
+"Sets healthchecks for an application.\n"
+"\n"
+"By default, Workflow only checks that the application starts in their "
+"Container. A health\n"
+"check may be added by configuring a health check probe for the application. "
+"The health\n"
+"checks are implemented as Kubernetes Container Probes. A 'startupProbe' "
+"'livenessProbe' \n"
+"and a 'readinessProbe' can be configured, and each probe can be of type "
+"'httpGet', 'exec' \n"
+"or 'tcpSocket' depending on the type of probe the Container requires.\n"
+"\n"
+"A 'startupProbe' indicates whether the application within the container is "
+"started.\n"
+"All other probes are disabled if a startup probe is provided, until it "
+"succeeds.\n"
+"If the startup probe fails, the container is subjected to its restart "
+"policy.\n"
+"\n"
+"A 'livenessProbe' is useful for applications running for long periods of "
+"time, eventually\n"
+"transitioning to broken states and cannot recover except by restarting "
+"them.\n"
+"\n"
+"Other times, a 'readinessProbe' is useful when the Container is only "
+"temporarily unable\n"
+"to serve, and will recover on its own. In this case, if a Container fails "
+"its 'readinessProbe'\n"
+", the Container will not be shut down, but rather the Container will stop "
+"receiving\n"
+"incoming requests.\n"
+"\n"
+"'httpGet' probes are just as it sounds: it performs a HTTP GET operation on "
+"the Container.\n"
+"A response code inside the 200-399 range is considered a pass. 'httpGet' "
+"probes accept a\n"
+"port number to perform the HTTP GET operation on the Container.\n"
+"\n"
+"'exec' probes run a command inside the Container to determine its health. An "
+"exit code of\n"
+"zero is considered a pass, while a non-zero status code is considered a "
+"fail. 'exec'\n"
+"probes accept a string of arguments to be run inside the Container.\n"
+"\n"
+"'tcpSocket' probes attempt to open a socket in the Container. The Container "
+"is only\n"
+"considered healthy if the check can establish a connection. 'tcpSocket' "
+"probes accept a\n"
+"port number to perform the socket connection on the Container.\n"
+msgstr ""
+
+#: internal/parser/labels.go:60
+msgid ""
+"Sets labels for an application.\n"
+"\n"
+"A label is a key/value pair used to label an application. This label is a "
+"general information for drycc user.\n"
+"Mostly used for administration/maintenance information, note for "
+"application. This information isn't send to scheduler."
+msgstr ""
+
+#: internal/parser/registry.go:76
+msgid ""
+"Sets registry information for an application. These credentials are the same "
+"as those used for\n"
+"'podmain login' to the private registry."
+msgstr ""
+
+#: internal/parser/limits.go:70
+msgid ""
+"Sets resource limits for an application.\n"
+"\n"
+"A resource limit is a finite resource within a pod which we can apply\n"
+"restrictions through Kubernetes."
+msgstr ""
+
+#: internal/parser/tags.go:87
+msgid ""
+"Sets tags for an application.\n"
+"\n"
+"A tag is a key/value pair used to tag an application's containers and is "
+"passed to the\n"
+"scheduler. This is often used to restrict workloads to specific hosts "
+"matching the\n"
+"scheduler-configured metadata."
+msgstr ""
+
+#: internal/parser/timeouts.go:65
+msgid "Sets termination grace period for an application"
+msgstr ""
+
+#: cmd/root.go:70
+msgid "Shortcut Commands:"
+msgstr "Shortcut Commands:"
+
+#: internal/parser/certs.go:41
+msgid "Show certificate information for an SSL application"
+msgstr ""
+
+#: internal/parser/resources.go:159
+msgid "Show extra details provided to resource"
+msgstr ""
+
+#: internal/parser/update.go:20
+msgid "Simulate an update, only print the version info."
+msgstr ""
+
+#: internal/parser/apps.go:197
+msgid ""
+"Skips the prompt for the application name. \n"
+"<app> is the uniquely identifiable name for the application"
+msgstr ""
+
+#: internal/parser/apps.go:134 internal/parser/ps.go:76
+msgid "Specify if the logs should be streamed"
+msgstr ""
+
+#: internal/parser/resources.go:115 internal/parser/resources.go:199
+msgid "Specify values in a YAML file. If set, params will be discard"
+msgstr ""
+
+#: internal/parser/ps.go:113
+msgid "Stdin is a TTY"
+msgstr ""
+
+#: internal/parser/autoscale.go:74
+msgid "Target CPU utilization percentage"
+msgstr ""
+
+#: cmd/root.go:27
+msgid "The Drycc command-line client issues API calls to a Drycc controller"
+msgstr "Drycc命令行客户端向Drycc控制器发出API调用"
+
+#: internal/parser/healthchecks.go:181
+msgid "The HTTP headers to send for 'httpGet' probes, separated by commas"
+msgstr ""
+
+#: internal/parser/services.go:65 internal/parser/services.go:116
+msgid "The IP protocol for this port. Supports TCP, UDP, and SCTP"
+msgstr ""
+
+#: internal/parser/healthchecks.go:88
+msgid ""
+"The arguments required for the healthcheck probe. 'exec', accepts a list of "
+"arguments;\n"
+"                  'httpGet' and 'tcpSocket' accept a port number."
+msgstr ""
+
+#: internal/parser/volumes.go:211
+msgid "The client used to manage volume files"
+msgstr ""
+
+#: internal/parser/volumes.go:233 internal/parser/apps.go:158
+#: internal/parser/shortcuts.go:44
+msgid "The filesystem path"
+msgstr ""
+
+#: internal/parser/config.go:34 internal/parser/config.go:64
+msgid "The group for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:157
+msgid "The group for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/config.go:194
+msgid "The group for which the config needs to be push"
+msgstr ""
+
+#: internal/parser/config.go:93
+msgid "The group for which the config needs to be set"
+msgstr ""
+
+#: internal/parser/config.go:122
+msgid "The group for which the config needs to be unset"
+msgstr ""
+
+#: internal/parser/config.go:218
+msgid "The group that requires attach configurations"
+msgstr ""
+
+#: internal/parser/config.go:245
+msgid "The group that requires detach configurations"
+msgstr "The group that requires detach configurations"
+
+#: internal/parser/healthchecks.go:86
+msgid ""
+"The healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr ""
+
+#: internal/parser/volumes.go:85
+msgid "The hostname or ip address of the nfs server"
+msgstr ""
+
+#: internal/parser/tokens.go:86
+msgid "The id of the token for controller authentication"
+msgstr ""
+
+#: internal/parser/healthchecks.go:182
+msgid "The initial delay timeout for the probe"
+msgstr ""
+
+#: internal/parser/limits.go:66
+msgid "The limit plan id to apply to the process type"
+msgstr ""
+
+#: internal/parser/apps.go:135
+msgid "The max seconds of followz the log stream"
+msgstr ""
+
+#: internal/parser/certs.go:23 internal/parser/certs.go:48
+#: internal/parser/tokens.go:22 internal/parser/tokens.go:40
+#: internal/parser/users.go:21 internal/parser/users.go:39
+#: internal/parser/volumes.go:23 internal/parser/volumes.go:139
+#: internal/parser/perms.go:22 internal/parser/perms.go:44
+#: internal/parser/releases.go:33 internal/parser/releases.go:62
+#: internal/parser/routes.go:27 internal/parser/routes.go:107
+#: internal/parser/domains.go:22 internal/parser/domains.go:43
+#: internal/parser/gateways.go:22 internal/parser/gateways.go:44
+#: internal/parser/limits.go:126 internal/parser/limits.go:150
+#: internal/parser/resources.go:24 internal/parser/resources.go:51
+#: internal/parser/resources.go:76 internal/parser/resources.go:130
+msgid "The maximum number of results to display"
+msgstr ""
+
+#: internal/parser/certs.go:107
+msgid "The name of the cert to get information from"
+msgstr ""
+
+#: internal/parser/certs.go:85
+msgid "The name of the cert to remove from the app"
+msgstr ""
+
+#: internal/parser/perms.go:65 internal/parser/perms.go:95
+#: internal/parser/perms.go:123
+msgid "The name of the user"
+msgstr ""
+
+#: internal/parser/apps.go:133
+msgid "The number of lines to display"
+msgstr ""
+
+#: internal/parser/ps.go:75
+msgid "The number of lines to display, -1 showing all log lines"
+msgstr ""
+
+#: internal/parser/pts.go:120 internal/parser/shortcuts.go:83
+msgid "The number of processes"
+msgstr ""
+
+#: internal/parser/healthchecks.go:183
+msgid "The number of seconds after which the probe times out"
+msgstr ""
+
+#: internal/parser/registry.go:72
+msgid "The password of the registry"
+msgstr ""
+
+#: internal/parser/ps.go:99 internal/parser/ps.go:130 internal/parser/ps.go:152
+#: internal/parser/shortcuts.go:58
+msgid "The pod name for the application"
+msgstr ""
+
+#: internal/parser/services.go:100
+msgid "The port exposed by this service"
+msgstr "The port exposed by this service"
+
+#: internal/parser/services.go:51
+msgid "The port that will be exposed by this service"
+msgstr ""
+
+#: internal/parser/certs.go:61
+msgid "The private key of the SSL certificate"
+msgstr ""
+
+#: internal/parser/volumes.go:232 internal/parser/volumes.go:257
+#: internal/parser/pts.go:61 internal/parser/pts.go:119
+#: internal/parser/pts.go:153 internal/parser/shortcuts.go:82
+#: internal/parser/tags.go:26 internal/parser/tags.go:60
+#: internal/parser/tags.go:100 internal/parser/tags.go:130
+msgid "The process name as defined in your Procfile"
+msgstr ""
+
+#: internal/parser/releases.go:130
+msgid "The process name as defined in your Procfile, such as 'web'"
+msgstr ""
+
+#: internal/parser/timeouts.go:60 internal/parser/timeouts.go:86
+msgid "The process type as defined in your Procfile"
+msgstr ""
+
+#: internal/parser/limits.go:95
+msgid "The process type as defined in your Procfile, such as 'web' or 'worker'"
+msgstr ""
+
+#: internal/parser/limits.go:65
+msgid ""
+"The process type as defined in your Procfile, such as 'web' or 'worker'."
+msgstr ""
+
+#: internal/parser/autoscale.go:59
+msgid "The process type to add to the application's autoscale settings"
+msgstr ""
+
+#: internal/parser/autoscale.go:91
+msgid "The process type to remove from the application's autoscale settings"
+msgstr ""
+
+#: internal/parser/domains.go:62
+msgid "The ptype for domain"
+msgstr ""
+
+#: internal/parser/registry.go:26 internal/parser/registry.go:50
+#: internal/parser/registry.go:85 internal/parser/registry.go:106
+msgid "The ptype for registry"
+msgstr ""
+
+#: internal/parser/config.go:33 internal/parser/config.go:63
+msgid "The ptype for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:155
+msgid "The ptype for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/config.go:193
+msgid "The ptype for which the config needs to be push"
+msgstr ""
+
+#: internal/parser/config.go:92
+msgid "The ptype for which the config needs to be set"
+msgstr ""
+
+#: internal/parser/config.go:121
+msgid "The ptype for which the config needs to be unset"
+msgstr ""
+
+#: internal/parser/healthchecks.go:30
+msgid "The ptype for which the health check needs to be removed"
+msgstr ""
+
+#: internal/parser/config.go:217
+msgid "The ptype that requires attach configurations"
+msgstr ""
+
+#: internal/parser/config.go:244
+msgid "The ptype that requires detach configurations"
+msgstr ""
+
+#: internal/parser/certs.go:60
+msgid "The public key of the SSL certificate"
+msgstr ""
+
+#: internal/parser/healthchecks.go:180
+msgid "The relative URL path for 'httpGet' probes"
+msgstr ""
+
+#: internal/parser/releases.go:77 internal/parser/releases.go:131
+msgid "The release of the application, such as '1'"
+msgstr ""
+
+#: internal/parser/resources.go:217 internal/parser/resources.go:242
+#: internal/parser/resources.go:264
+msgid "The resource instance alias name"
+msgstr ""
+
+#: internal/parser/resources.go:97 internal/parser/resources.go:180
+msgid "The resource instance parameters key"
+msgstr ""
+
+#: internal/parser/resources.go:98 internal/parser/resources.go:181
+msgid "The resource instance parameters value"
+msgstr ""
+
+#: internal/parser/resources.go:95 internal/parser/resources.go:178
+msgid "The resource's service"
+msgstr ""
+
+#: internal/parser/routes.go:52
+msgid "The route kind, range: HTTPRoute,TCPRoute,UDPRoute,GRPCRoute,TLSRoute"
+msgstr ""
+
+#: internal/parser/routes.go:53
+msgid "The route's backend, pattern: <service>:<port>,<weight>"
+msgstr ""
+
+#: internal/parser/resources.go:64
+msgid "The service name for plans"
+msgstr ""
+
+#: internal/parser/resources.go:96 internal/parser/resources.go:179
+msgid "The service's plan"
+msgstr ""
+
+#: internal/parser/apps.go:159 internal/parser/shortcuts.go:45
+msgid "The shell command to run inside the container"
+msgstr ""
+
+#: internal/parser/builds.go:57
+msgid "The stack name for the application"
+msgstr ""
+
+#: internal/parser/tags.go:82
+msgid "The tag key"
+msgstr ""
+
+#: internal/parser/tags.go:118
+msgid "The tag key to unset"
+msgstr ""
+
+#: internal/parser/tags.go:83
+msgid "The tag value"
+msgstr ""
+
+#: internal/parser/routes.go:187 internal/parser/routes.go:219
+msgid "The unique name of the gaetway"
+msgstr ""
+
+#: internal/parser/routes.go:51 internal/parser/routes.go:120
+#: internal/parser/routes.go:146 internal/parser/routes.go:176
+#: internal/parser/routes.go:208 internal/parser/routes.go:237
+msgid "The unique name of the route"
+msgstr ""
+
+#: internal/parser/certs.go:22 internal/parser/routing.go:19
+#: internal/parser/services.go:23 internal/parser/timeouts.go:21
+#: internal/parser/volumes.go:22 internal/parser/apps.go:88
+#: internal/parser/apps.go:107 internal/parser/apps.go:132
+#: internal/parser/apps.go:170 internal/parser/apps.go:196
+#: internal/parser/apps.go:217 internal/parser/builds.go:19
+#: internal/parser/builds.go:56 internal/parser/ps.go:20
+#: internal/parser/ps.go:112 internal/parser/pts.go:20
+#: internal/parser/pts.go:133 internal/parser/perms.go:21
+#: internal/parser/releases.go:32 internal/parser/routes.go:26
+#: internal/parser/git.go:17 internal/parser/autodeploy.go:21
+#: internal/parser/autorollback.go:19 internal/parser/autoscale.go:21
+#: internal/parser/config.go:29 internal/parser/domains.go:21
+#: internal/parser/gateways.go:21 internal/parser/labels.go:20
+#: internal/parser/limits.go:20 internal/parser/registry.go:25
+#: internal/parser/resources.go:23 internal/parser/tags.go:25
+msgid "The uniquely identifiable name for the application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:29
+msgid "The uniquely identifiable name of the application"
+msgstr ""
+
+#: internal/parser/perms.go:66
+msgid "The user permissions (view,change,delete)"
+msgstr ""
+
+#: internal/parser/perms.go:96
+msgid "The user's permissions (view,change,delete)"
+msgstr ""
+
+#: internal/parser/registry.go:71
+msgid "The username of the registry"
+msgstr ""
+
+#: internal/parser/users.go:78
+msgid "The username you want to disable"
+msgstr ""
+
+#: internal/parser/users.go:53
+msgid "The username you want to enable"
+msgstr ""
+
+#: internal/parser/timeouts.go:61
+msgid "The value to apply to the process type in seconds"
+msgstr ""
+
+#: internal/parser/builds.go:20 internal/parser/builds.go:79
+#: internal/parser/builds.go:104
+msgid "The version for which the build info needs to be displayed"
+msgstr ""
+
+#: internal/parser/config.go:35 internal/parser/config.go:65
+msgid "The version for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:156
+msgid "The version for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/healthchecks.go:31 internal/parser/healthchecks.go:56
+msgid "The version for which the health check needs to be listed"
+msgstr ""
+
+#: internal/parser/limits.go:21 internal/parser/limits.go:46
+msgid "The version for which the limit needs to be listed"
+msgstr ""
+
+#: internal/parser/registry.go:27 internal/parser/registry.go:51
+msgid "The version for which the registry needs to be listed"
+msgstr ""
+
+#: internal/parser/tags.go:27 internal/parser/tags.go:61
+msgid "The version for which the tag needs to be listed"
+msgstr ""
+
+#: internal/parser/timeouts.go:22 internal/parser/timeouts.go:44
+msgid "The version for which the timeout needs to be listed"
+msgstr ""
+
+#: internal/parser/volumes.go:57 internal/parser/volumes.go:112
+#: internal/parser/volumes.go:231 internal/parser/volumes.go:256
+#: internal/parser/apps.go:157 internal/parser/shortcuts.go:43
+msgid "The volume name"
+msgstr ""
+
+#: internal/parser/volumes.go:155
+msgid "The volume name to be info"
+msgstr ""
+
+#: internal/parser/volumes.go:181
+msgid "The volume name to be removed"
+msgstr ""
+
+#: internal/parser/volumes.go:58 internal/parser/volumes.go:113
+msgid "The volume size, such as '500G'"
+msgstr ""
+
+#: internal/parser/volumes.go:84
+msgid "The volume type, such as csi, nfs, oss"
+msgstr ""
+
+#: internal/parser/resources.go:94 internal/parser/resources.go:148
+#: internal/parser/resources.go:177
+msgid "This resource instance alias"
+msgstr ""
+
+#: internal/parser/tokens.go:98 internal/parser/builds.go:60
+#: internal/parser/builds.go:107 internal/parser/pts.go:98
+#: internal/parser/releases.go:118
+msgid "To proceed, type \"yes\""
+msgstr ""
+
+#: internal/parser/config.go:94 internal/parser/config.go:123
+#: internal/parser/config.go:196
+msgid "To proceed, type 'yes'"
+msgstr ""
+
+#: internal/parser/apps.go:209
+msgid "Transfer app ownership to another user"
+msgstr ""
+
+#: internal/parser/apps.go:210
+msgid "Transfer application ownership to another user."
+msgstr ""
+
+#: internal/parser/autoscale.go:95
+msgid "Turn off autoscale for an app"
+msgstr ""
+
+#: internal/parser/autoscale.go:63
+msgid "Turn on autoscale for an app"
+msgstr ""
+
+#: internal/parser/domains.go:74
+msgid "Unbinds a domain for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:260
+msgid "Unmount a volume from process of the application"
+msgstr ""
+
+#: internal/parser/autoscale.go:96
+msgid "Unset autoscale per process type for an app"
+msgstr ""
+
+#: internal/parser/config.go:114
+msgid "Unset environment variables for an app"
+msgstr ""
+
+#: internal/parser/healthchecks.go:209
+msgid "Unset healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:99
+msgid "Unset registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:98
+msgid "Unset resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:89
+msgid "Unset resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/tags.go:121
+msgid "Unset tags for an application"
+msgstr ""
+
+#: internal/parser/config.go:115
+msgid "Unsets an environment variable for an application or config group"
+msgstr ""
+
+#: internal/parser/labels.go:90
+msgid "Unsets labels for an application"
+msgstr ""
+
+#: internal/parser/registry.go:100
+msgid "Unsets registry information for an application"
+msgstr ""
+
+#: internal/parser/timeouts.go:90
+msgid ""
+"Unsets timeouts for an application. Default value (30s) or set by drycc "
+"controller"
+msgstr ""
+
+#: internal/parser/resources.go:184
+msgid "Update a resource from the application"
+msgstr ""
+
+#: internal/parser/perms.go:99
+msgid "Update a user permission"
+msgstr ""
+
+#: internal/parser/update.go:14 internal/parser/update.go:15
+msgid "Update workflow cli to latest release"
+msgstr ""
+
+#: internal/parser/tls.go:39
+msgid "View TLS settings information"
+msgstr ""
+
+#: internal/parser/routing.go:33
+msgid "View application's routability information"
+msgstr ""
+
+#: internal/parser/autorollback.go:33
+msgid "View autorollback info for an application"
+msgstr ""
+
+#: internal/parser/apps.go:81
+msgid "View info about an application"
+msgstr ""
+
+#: internal/parser/apps.go:171
+msgid "Volume mounts in format 'volume:path'"
+msgstr ""
+
+#: internal/parser/volumes.go:206
+msgid ""
+"arguments for running commands, when cmd is 'cp', args should be '[source] "
+"[dest]'.\n"
+"           volume path format 'vol://volumename/', '/' is equivalent to the "
+"mount path."
+msgstr ""
+
+#: internal/parser/builds.go:44
+msgid "imports an image and deploys as a new release"
+msgstr ""
+
+#: internal/parser/version.go:22
+msgid "list api and controller versions."
+msgstr ""
+
+#: internal/parser/volumes.go:203
+msgid ""
+"ls         list volume files\n"
+"           cp         copy volume files\n"
+"           rm         remove volume files"
+msgstr ""
+
+#: internal/parser/autoscale.go:72
+msgid "minimum replicas to keep around"
+msgstr ""
+
+#: internal/parser/services.go:99
+msgid "procfile type which should handle the request, e.g. webhooks"
+msgstr "procfile type which should handle the request, e.g. webhooks"
+
+#: internal/parser/services.go:49
+msgid ""
+"procfile type which should handle the request, e.g. webhooks (should be bind "
+"to the port PORT)\n"
+"             only single extra service per Porcfile type could be created."
+msgstr ""
+
+#: internal/parser/volumes.go:91
+msgid "secret access key for authentication"
+msgstr ""
+
+#: internal/parser/resources.go:228
+msgid ""
+"skips the prompt for the resource name. <resource> is the uniquely "
+"identifiable\n"
+"name for the resource"
+msgstr ""
+
+#: internal/parser/healthchecks.go:87
+msgid "the healthcheck probe type, such as 'httpGet', 'exec' or 'tcpSocket'"
+msgstr ""
+
+#: internal/parser/healthchecks.go:206
+msgid ""
+"the healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr ""
+
+#: internal/parser/labels.go:84
+msgid "the label key to unset, for example: \"git_repo\" or \"team\""
+msgstr ""
+
+#: internal/parser/labels.go:55
+msgid "the label key, for example: \"git_repo\" or \"team\""
+msgstr ""
+
+#: internal/parser/labels.go:56
+msgid ""
+"the label value, for example: \"https://github.com/drycc/workflow\" or "
+"\"frontend\""
+msgstr ""
+
+#: internal/parser/resources.go:267
+msgid "unbind a resources for an application"
+msgstr ""
+
+#~ msgid ""
+#~ "procfile type which should handle the request, e.g. webhooks (should be "
+#~ "bind to the port PORT)"
+#~ msgstr ""
+#~ "procfile type which should handle the request, e.g. webhooks (should be "
+#~ "bind to the port PORT)"
+
+#~ msgid "Shortcut commands, use 'drycc shortcuts' to see all:"
+#~ msgstr "Shortcut commands, use 'drycc shortcuts' to see all:"
diff --git a/pkg/i18n/translations/drycc/template.pot b/pkg/i18n/translations/drycc/template.pot
new file mode 100644
index 000000000..34ae2f3d0
--- /dev/null
+++ b/pkg/i18n/translations/drycc/template.pot
@@ -0,0 +1,1717 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2025-03-31 14:12+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: internal/parser/builds.go:58 internal/parser/builds.go:105
+msgid "A YAML file used to supply a Procfile to the application"
+msgstr ""
+
+#: internal/parser/keys.go:52
+msgid "A local file path to an SSH public key used to push application code"
+msgstr ""
+
+#: internal/parser/config.go:158 internal/parser/config.go:195
+msgid "A path leading to an environment file"
+msgstr ""
+
+#: internal/parser/tls.go:121
+msgid "ACME account email"
+msgstr ""
+
+#: internal/parser/tls.go:122
+msgid "ACME server URL"
+msgstr ""
+
+#: internal/parser/volumes.go:90
+msgid "Access key id for authentication"
+msgstr ""
+
+#: internal/parser/tls.go:55 internal/parser/tls.go:83
+msgid "Action to perform, 'enable' or 'disable'"
+msgstr ""
+
+#: internal/parser/tokens.go:58
+msgid "Add a token for controller authentication"
+msgstr ""
+
+#: internal/parser/perms.go:69
+msgid "Add a user permissions"
+msgstr ""
+
+#: internal/parser/keys.go:56
+msgid "Add an SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:65
+msgid "Add an SSL certificate to an app"
+msgstr ""
+
+#: internal/parser/labels.go:59
+msgid "Add new application's label"
+msgstr ""
+
+#: internal/parser/keys.go:57
+msgid "Adds SSH keys for the logged in user"
+msgstr ""
+
+#: internal/parser/git.go:35
+msgid "Adds git remote of application to repository"
+msgstr ""
+
+#: internal/parser/config.go:160
+msgid "Allows you to have the pull overwrite keys to the path"
+msgstr ""
+
+#: internal/parser/config.go:57
+msgid "An app config info"
+msgstr ""
+
+#: internal/parser/tls.go:23
+msgid "Application name"
+msgstr ""
+
+#: internal/parser/certs.go:136
+msgid "Attach a certificate to a domain"
+msgstr ""
+
+#: internal/parser/certs.go:135
+msgid "Attach an SSL certificate to a domain"
+msgstr ""
+
+#: internal/parser/routes.go:179
+msgid "Attach to gateway"
+msgstr ""
+
+#: internal/parser/auth.go:34
+msgid "Authenticate against a controller"
+msgstr ""
+
+#: internal/parser/tls.go:88
+msgid "Automatic certificate management"
+msgstr ""
+
+#: internal/parser/domains.go:55
+msgid "Bind a domain to an application"
+msgstr ""
+
+#: internal/parser/resources.go:245
+msgid "Bind a resource for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:89
+msgid "Bucket name in object storage"
+msgstr ""
+
+#: internal/parser/tls.go:123
+msgid "CA key ID"
+msgstr ""
+
+#: internal/parser/tls.go:124
+msgid "CA key secret"
+msgstr ""
+
+#: internal/parser/pts.go:156
+msgid "Clean process types of not used"
+msgstr ""
+
+#: internal/parser/auth.go:53
+msgid "Clear the current user session"
+msgstr ""
+
+#: internal/parser/apps.go:172
+msgid "Command execution timeout in seconds"
+msgstr ""
+
+#: internal/parser/certs.go:131
+msgid ""
+"Common name of the domain to attach to (needs to already be in the system)"
+msgstr ""
+
+#: internal/parser/certs.go:156
+msgid "Common name of the domain to detach from"
+msgstr ""
+
+#: internal/parser/tls.go:115
+msgid "Configure automatic certificate management issuer details"
+msgstr ""
+
+#: internal/parser/apps.go:44
+msgid "Create a new application"
+msgstr ""
+
+#: internal/parser/resources.go:101
+msgid "Create a resource for the application"
+msgstr ""
+
+#: internal/parser/routes.go:56
+msgid "Create a route for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:61
+msgid "Create a volume for the application"
+msgstr ""
+
+#: internal/parser/gateways.go:59
+msgid "Create gateways for an application"
+msgstr ""
+
+#: internal/parser/services.go:55
+msgid "Create service for an application"
+msgstr ""
+
+#: internal/parser/apps.go:45
+msgid ""
+"Creates a new application. If no <id> is provided, one will be generated "
+"automatically"
+msgstr ""
+
+#: internal/parser/builds.go:45
+msgid ""
+"Creates a new build of an application. Imports an <image> and deploys it to "
+"Drycc\n"
+"as a new release. If a Procfile or drycc.yaml is present in the current "
+"directory,\n"
+"it will be used as the default for this application."
+msgstr ""
+
+#: internal/parser/services.go:56
+msgid ""
+"Creates extra service for an application and binds it to specific route of "
+"the main app domain"
+msgstr ""
+
+#: internal/parser/gateways.go:60
+msgid ""
+"Creates gateways for an application and binds it to allow listener of the "
+"main app domain"
+msgstr ""
+
+#: internal/parser/routes.go:57
+msgid "Creates routes for an application, provides a way to route requests"
+msgstr ""
+
+#: internal/parser/resources.go:220
+msgid "Delete a resource from the application"
+msgstr ""
+
+#: internal/parser/volumes.go:184
+msgid "Delete a volume from the application"
+msgstr ""
+
+#: internal/parser/ps.go:155
+msgid "Delete the selected processes"
+msgstr ""
+
+#: internal/parser/gateways.go:88
+msgid "Deletes specific gateway for application"
+msgstr ""
+
+#: internal/parser/releases.go:109
+msgid "Deploy the latest release by process types"
+msgstr ""
+
+#: internal/parser/pts.go:64
+msgid "Describe a process type"
+msgstr ""
+
+#: internal/parser/apps.go:190
+msgid "Destroy an application"
+msgstr ""
+
+#: internal/parser/certs.go:160
+msgid "Detach an SSL certificate from a domain"
+msgstr ""
+
+#: internal/parser/certs.go:161
+msgid "Detach certificate from a domain"
+msgstr ""
+
+#: internal/parser/routes.go:211
+msgid "Dettach to gateway"
+msgstr ""
+
+#: internal/parser/users.go:81
+msgid "Disable a user"
+msgstr ""
+
+#: internal/parser/users.go:82
+msgid ""
+"Disable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+
+#: internal/parser/autorollback.go:58
+msgid "Disable autorollback for an application"
+msgstr ""
+
+#: internal/parser/routing.go:57
+msgid "Disable routing for an application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:59
+msgid "Disables autodeploy for an app"
+msgstr ""
+
+#: cmd/root.go:34 internal/parser/version.go:16
+msgid "Display client version"
+msgstr ""
+
+#: cmd/root.go:35
+msgid "Display help information"
+msgstr ""
+
+#: internal/parser/auth.go:71
+msgid "Display the current user"
+msgstr ""
+
+#: internal/parser/version.go:17
+msgid "Displays the client version"
+msgstr ""
+
+#: internal/parser/auth.go:72
+msgid "Displays the currently logged in user"
+msgstr ""
+
+#: internal/parser/apps.go:56
+msgid "Do not create a 'drycc' git remote"
+msgstr ""
+
+#: internal/parser/builds.go:59 internal/parser/builds.go:106
+msgid "Drycc config path to the application"
+msgstr ""
+
+#: internal/parser/users.go:56
+msgid "Enable a user"
+msgstr ""
+
+#: internal/parser/users.go:57
+msgid ""
+"Enable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+
+#: internal/parser/autorollback.go:46
+msgid "Enable autorollback for an application"
+msgstr ""
+
+#: internal/parser/routing.go:45
+msgid "Enable routing for an application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:47
+msgid "Enables autodeploy for an app"
+msgstr ""
+
+#: internal/parser/auth.go:43
+msgid "Enables or disables SSL certificate verification for API requests"
+msgstr ""
+
+#: internal/parser/volumes.go:88
+msgid "Endpoint url for object storage service"
+msgstr ""
+
+#: internal/parser/ps.go:102
+msgid "Execute a command in a container"
+msgstr ""
+
+#: internal/parser/volumes.go:116
+msgid "Expand a volume capacity for the application"
+msgstr ""
+
+#: internal/parser/config.go:145
+msgid ""
+"Extract all environment variables from an application or config group. for "
+"local use.\n"
+"\n"
+"The environmental variables can be piped into a file, 'drycc config:pull > "
+"file',\n"
+"or stored locally in a file named .env. This file can be\n"
+"read by foreman to load the local environment for your app."
+msgstr ""
+
+#: internal/parser/auth.go:78
+msgid "Fetch detailed user information"
+msgstr ""
+
+#: internal/parser/certs.go:112
+msgid "Fetch more detailed information about a certificate"
+msgstr ""
+
+#: internal/parser/tls.go:60
+msgid "Force TLS settings to HTTPS-only redirection"
+msgstr ""
+
+#: internal/parser/releases.go:117
+msgid "Force deploy"
+msgstr ""
+
+#: internal/parser/resources.go:151
+msgid "Get a resource's detail in the application"
+msgstr ""
+
+#: internal/parser/certs.go:111
+msgid "Get detailed informaton about the certificate"
+msgstr ""
+
+#: internal/parser/routes.go:123
+msgid "Get route rules"
+msgstr ""
+
+#: internal/parser/routes.go:124
+msgid "Get route rules for an application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:184
+msgid "How often (in seconds) to perform the probe"
+msgstr ""
+
+#: internal/parser/keys.go:29
+msgid "List SSH keys for the logged in user"
+msgstr ""
+
+#: internal/parser/certs.go:40
+msgid "List SSL certificates for an application"
+msgstr ""
+
+#: internal/parser/apps.go:65
+msgid "List accessible applications"
+msgstr ""
+
+#: internal/parser/limits.go:140
+msgid "List all available limit plans"
+msgstr ""
+
+#: internal/parser/limits.go:118
+msgid "List all available limit specs"
+msgstr ""
+
+#: internal/parser/resources.go:67
+msgid "List all available plans for a resource service"
+msgstr ""
+
+#: internal/parser/resources.go:44
+msgid "List all available resource services"
+msgstr ""
+
+#: internal/parser/users.go:32
+msgid "List all registered users"
+msgstr ""
+
+#: internal/parser/perms.go:37
+msgid "List all user permissions"
+msgstr ""
+
+#: internal/parser/releases.go:53
+msgid "List an application's release history"
+msgstr ""
+
+#: internal/parser/gateways.go:36
+msgid "List application gateways"
+msgstr ""
+
+#: internal/parser/ps.go:37
+msgid "List application pods"
+msgstr ""
+
+#: internal/parser/pts.go:37
+msgid "List application process types"
+msgstr ""
+
+#: internal/parser/routes.go:100
+msgid "List application routes"
+msgstr ""
+
+#: internal/parser/services.go:76
+msgid "List application services"
+msgstr ""
+
+#: internal/parser/labels.go:33
+msgid "List application's labels"
+msgstr ""
+
+#: internal/parser/autoscale.go:34
+msgid "List autoscale options for an application"
+msgstr ""
+
+#: internal/parser/domains.go:36
+msgid "List domains bound to an application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:50
+msgid "List healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:43
+msgid "List registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:40
+msgid "List resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:38
+msgid "List resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/resources.go:123
+msgid "List resources in the application"
+msgstr ""
+
+#: internal/parser/limits.go:117
+msgid "List specification information of the server"
+msgstr ""
+
+#: internal/parser/tags.go:52
+msgid "List tags for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:132
+msgid "List volumes in the application"
+msgstr ""
+
+#: internal/parser/apps.go:66
+msgid "Lists applications visible to the current user"
+msgstr ""
+
+#: internal/parser/gateways.go:37
+msgid "Lists gateways for an application"
+msgstr ""
+
+#: internal/parser/pts.go:38
+msgid "Lists process types servicing an application"
+msgstr ""
+
+#: internal/parser/ps.go:38
+msgid "Lists processes servicing an application"
+msgstr ""
+
+#: internal/parser/registry.go:44
+msgid "Lists registry information for an application"
+msgstr ""
+
+#: internal/parser/releases.go:54
+msgid "Lists release history for an application"
+msgstr ""
+
+#: internal/parser/tokens.go:33
+msgid "Lists tokens visible to the current controller"
+msgstr ""
+
+#: internal/parser/auth.go:35
+msgid "Logs in by authenticating against a controller"
+msgstr ""
+
+#: internal/parser/auth.go:54
+msgid "Logs out from a controller and clears the user session"
+msgstr ""
+
+#: internal/parser/certs.go:15
+msgid "Manage SSL endpoints for an app"
+msgstr ""
+
+#: internal/parser/tls.go:17
+msgid "Manage TLS/SSL settings for applications"
+msgstr ""
+
+#: internal/parser/domains.go:14
+msgid "Manage and assign domain names to your applications"
+msgstr ""
+
+#: internal/parser/healthchecks.go:23
+msgid "Manage application healthchecks"
+msgstr ""
+
+#: internal/parser/apps.go:17
+msgid "Manage applications used to provide services"
+msgstr ""
+
+#: internal/parser/auth.go:13
+msgid "Manage authentication"
+msgstr ""
+
+#: internal/parser/autodeploy.go:15
+msgid "Manage autodeploy if or not for applications"
+msgstr ""
+
+#: internal/parser/autorollback.go:14
+msgid "Manage autorollback if or not for application"
+msgstr ""
+
+#: internal/parser/autoscale.go:15
+msgid "Manage autoscale for applications"
+msgstr ""
+
+#: internal/parser/builds.go:13
+msgid "Manage builds created using 'git push'"
+msgstr ""
+
+#: internal/parser/config.go:23
+msgid "Manage environment variables that define app config"
+msgstr ""
+
+#: internal/parser/gateways.go:14
+msgid "Manage gateways for your applications"
+msgstr ""
+
+#: internal/parser/git.go:13
+msgid "Manage git for applications"
+msgstr ""
+
+#: internal/parser/labels.go:14
+msgid "Manage labels of application"
+msgstr ""
+
+#: internal/parser/perms.go:14
+msgid "Manage permissions for applications"
+msgstr ""
+
+#: internal/parser/timeouts.go:15
+msgid "Manage pods termination grace period"
+msgstr ""
+
+#: internal/parser/registry.go:19
+msgid "Manage private registry information for your application"
+msgstr ""
+
+#: internal/parser/pts.go:14
+msgid "Manage process types inside an app"
+msgstr ""
+
+#: internal/parser/ps.go:14
+msgid "Manage processes inside an app"
+msgstr ""
+
+#: internal/parser/users.go:14
+msgid "Manage registered users"
+msgstr ""
+
+#: internal/parser/releases.go:24
+msgid "Manage releases of an application"
+msgstr ""
+
+#: internal/parser/limits.go:14
+msgid "Manage resource limits for your application"
+msgstr ""
+
+#: internal/parser/resources.go:16
+msgid "Manage resources for your applications"
+msgstr ""
+
+#: internal/parser/routing.go:13
+msgid "Manage routability of an application"
+msgstr ""
+
+#: internal/parser/routes.go:19
+msgid "Manage routes for your applications"
+msgstr ""
+
+#: internal/parser/services.go:17
+msgid "Manage services for your applications"
+msgstr ""
+
+#: internal/parser/keys.go:14
+msgid "Manage ssh keys used for 'git push' deployments"
+msgstr ""
+
+#: internal/parser/tags.go:19
+msgid "Manage tags for application containers"
+msgstr ""
+
+#: internal/parser/tokens.go:15
+msgid "Manage user tokens"
+msgstr ""
+
+#: internal/parser/volumes.go:15
+msgid "Manage volumes for your applications"
+msgstr ""
+
+#: internal/parser/autoscale.go:73
+msgid "Max replicas to scale up to"
+msgstr ""
+
+#: internal/parser/apps.go:22 internal/parser/apps.go:72
+msgid "Maximum number of results to display"
+msgstr ""
+
+#: internal/parser/healthchecks.go:186
+msgid ""
+"Minimum consecutive successes for the probe to be considered failed after "
+"having succeeded"
+msgstr ""
+
+#: internal/parser/healthchecks.go:185
+msgid ""
+"Minimum consecutive successes for the probe to be considered successful "
+"after having failed"
+msgstr ""
+
+#: internal/parser/volumes.go:236
+msgid "Mount a volume to process of the application"
+msgstr ""
+
+#: internal/parser/apps.go:55 internal/parser/git.go:41
+msgid "Name of remote to create"
+msgstr ""
+
+#: internal/parser/keys.go:51
+msgid "Name of the SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:130
+msgid "Name of the certificate to attach domain to"
+msgstr ""
+
+#: internal/parser/certs.go:155
+msgid "Name of the certificate to deatch from a domain"
+msgstr ""
+
+#: internal/parser/certs.go:59
+msgid "Name of the certificate to reference it by"
+msgstr ""
+
+#: internal/parser/services.go:52
+msgid ""
+"Number or name of the port to access on the pods targeted by the service"
+msgstr ""
+
+#: internal/parser/apps.go:100
+msgid "Open the application in a browser"
+msgstr ""
+
+#: internal/parser/apps.go:101
+msgid "Opens a URL to the application in the default browser"
+msgstr ""
+
+#: internal/parser/git.go:42
+msgid "Overwrite remote of the given name if it already exists"
+msgstr ""
+
+#: internal/parser/ps.go:114
+msgid "Pass stdin to the container"
+msgstr ""
+
+#: internal/parser/volumes.go:86
+msgid "Path that is exported by the nfs server"
+msgstr ""
+
+#: cmd/root.go:33
+msgid "Path to configuration file."
+msgstr ""
+
+#: internal/parser/routes.go:188 internal/parser/routes.go:220
+msgid "Port is the network port, the gateway listener expects to receive"
+msgstr ""
+
+#: internal/parser/gateways.go:67
+msgid "Port is the network port, the listener expects to receive"
+msgstr ""
+
+#: internal/parser/gateways.go:96
+msgid "Port is the network port, the listener received"
+msgstr ""
+
+#: internal/parser/ps.go:133
+msgid "Print a detailed description of the selected process"
+msgstr ""
+
+#: internal/parser/pts.go:65
+msgid "Print a detailed description of the selected process type"
+msgstr ""
+
+#: internal/parser/builds.go:74
+msgid "Print information about a specific build"
+msgstr ""
+
+#: internal/parser/releases.go:80
+msgid "Print information about a specific release"
+msgstr ""
+
+#: internal/parser/volumes.go:158
+msgid "Print information about a volume"
+msgstr ""
+
+#: internal/parser/builds.go:99
+msgid "Print process info about a specific build"
+msgstr ""
+
+#: internal/parser/ps.go:63
+msgid "Print the logs for a container"
+msgstr ""
+
+#: internal/parser/ps.go:64
+msgid "Print the logs for a container in a pod or specified resource"
+msgstr ""
+
+#: internal/parser/ps.go:78
+msgid ""
+"Print the logs for the previous instance of the container in a pod if it "
+"exists"
+msgstr ""
+
+#: internal/parser/ps.go:77
+msgid "Print the logs of this container"
+msgstr ""
+
+#: internal/parser/autoscale.go:35
+msgid "Prints a list of autoscale options for the application"
+msgstr ""
+
+#: internal/parser/labels.go:34
+msgid "Prints a list of labels of the application"
+msgstr ""
+
+#: internal/parser/releases.go:81
+msgid "Prints info about a particular release"
+msgstr ""
+
+#: internal/parser/apps.go:82
+msgid "Prints info about the current application"
+msgstr ""
+
+#: internal/parser/autodeploy.go:35
+msgid "Prints info about the current application's autodeploy if or not"
+msgstr ""
+
+#: internal/parser/autorollback.go:34
+msgid "Prints info about the current application's autorollback if or not"
+msgstr ""
+
+#: internal/parser/config.go:159
+msgid "Prompts for each value to be overwritten"
+msgstr ""
+
+#: internal/parser/gateways.go:68
+msgid ""
+"Protocol specifies the network protocol this listener expects to receive. "
+"Supports TCP, UDP, TLS, HTTP, and HTTPS"
+msgstr ""
+
+#: internal/parser/gateways.go:97
+msgid "Protocol specifies the network protocol this listener received"
+msgstr ""
+
+#: internal/parser/tokens.go:55
+msgid "Provide a alias for controller authentication token"
+msgstr ""
+
+#: internal/parser/tokens.go:68 internal/parser/auth.go:42
+msgid "Provide a password for the account"
+msgstr ""
+
+#: internal/parser/tokens.go:67 internal/parser/auth.go:41
+msgid "Provide a username for the account"
+msgstr ""
+
+#: internal/parser/config.go:144
+msgid "Pull environment variables to the path"
+msgstr ""
+
+#: internal/parser/config.go:182
+msgid "Push environment variables from the path"
+msgstr ""
+
+#: internal/parser/limits.go:148
+msgid "Query plans that meet the specified memory capacity, unit GiB"
+msgstr ""
+
+#: internal/parser/limits.go:147
+msgid "Query plans that meet the specified number of cpu cores"
+msgstr ""
+
+#: internal/parser/limits.go:149
+msgid "Query plans that meet the specified spec id, see [specs] subcommand."
+msgstr ""
+
+#: internal/parser/routes.go:240
+msgid "Remove a route from an application"
+msgstr ""
+
+#: internal/parser/tokens.go:89
+msgid "Remove a token for controller authentication"
+msgstr ""
+
+#: internal/parser/perms.go:126
+msgid "Remove a user permissions"
+msgstr ""
+
+#: internal/parser/keys.go:80
+msgid "Remove an SSH key"
+msgstr ""
+
+#: internal/parser/certs.go:89
+msgid "Remove an SSL certificate from an app"
+msgstr ""
+
+#: internal/parser/labels.go:89
+msgid "Remove application's label"
+msgstr ""
+
+#: internal/parser/gateways.go:87
+msgid "Remove gateways from an application"
+msgstr ""
+
+#: internal/parser/services.go:103
+msgid "Remove service from an application"
+msgstr ""
+
+#: internal/parser/keys.go:81
+msgid "Removes an SSH key for the logged in user"
+msgstr ""
+
+#: internal/parser/git.go:51
+msgid "Removes git remote of application from repository"
+msgstr ""
+
+#: internal/parser/pts.go:90
+msgid "Restart an application or process types"
+msgstr ""
+
+#: internal/parser/pts.go:89
+msgid "Restart application process types"
+msgstr ""
+
+#: internal/parser/apps.go:173
+msgid "Retention time of records in seconds"
+msgstr ""
+
+#: internal/parser/apps.go:125
+msgid "Retrieve application log events"
+msgstr ""
+
+#: internal/parser/apps.go:126
+msgid "Retrieves the most recent log events"
+msgstr ""
+
+#: internal/parser/releases.go:134
+msgid "Return to a previous release"
+msgstr ""
+
+#: internal/parser/releases.go:135
+msgid "Rolls back to a previous application release"
+msgstr ""
+
+#: internal/parser/routes.go:158
+msgid "Rules-file is the file name of rule to apply"
+msgstr ""
+
+#: internal/parser/apps.go:162
+msgid "Run a command in an ephemeral app container"
+msgstr ""
+
+#: internal/parser/apps.go:163
+msgid "Runs a command inside an ephemeral app container"
+msgstr ""
+
+#: internal/parser/builds.go:108
+msgid "Save process info to the local"
+msgstr ""
+
+#: internal/parser/pts.go:123
+msgid "Scale process types of replicas"
+msgstr ""
+
+#: internal/parser/pts.go:124
+msgid "Scales an application's processes by type"
+msgstr ""
+
+#: internal/parser/limits.go:125
+msgid ""
+"Search keywords separated by commas, matching must satisfy all of the "
+"specified."
+msgstr ""
+
+#: internal/parser/config.go:223
+msgid "Selects environment groups to attach an app ptype"
+msgstr ""
+
+#: internal/parser/config.go:248
+msgid "Selects environment groups to detach an app ptype"
+msgstr ""
+
+#: internal/parser/autoscale.go:64
+msgid "Set autoscale option per process type for an app"
+msgstr ""
+
+#: internal/parser/config.go:85
+msgid "Set environment variables for an app"
+msgstr ""
+
+#: internal/parser/healthchecks.go:92
+msgid "Set healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:75
+msgid "Set registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:69
+msgid "Set resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:64
+msgid "Set resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/routes.go:149
+msgid "Set route rules"
+msgstr ""
+
+#: internal/parser/routes.go:150
+msgid "Set route rules for an application"
+msgstr ""
+
+#: internal/parser/tags.go:86
+msgid "Set tags for an application"
+msgstr ""
+
+#: internal/parser/config.go:86
+msgid "Sets environment variables for an application or config group"
+msgstr ""
+
+#: internal/parser/config.go:183
+msgid ""
+"Sets environment variables for an application or config group.\n"
+"\n"
+"This file can be read by foreman\n"
+"to load the local environment for your app. The file should be piped via\n"
+"stdin, 'drycc config:push < .env', or using the --path option."
+msgstr ""
+
+#: internal/parser/healthchecks.go:93
+msgid ""
+"Sets healthchecks for an application.\n"
+"\n"
+"By default, Workflow only checks that the application starts in their "
+"Container. A health\n"
+"check may be added by configuring a health check probe for the application. "
+"The health\n"
+"checks are implemented as Kubernetes Container Probes. A 'startupProbe' "
+"'livenessProbe' \n"
+"and a 'readinessProbe' can be configured, and each probe can be of type "
+"'httpGet', 'exec' \n"
+"or 'tcpSocket' depending on the type of probe the Container requires.\n"
+"\n"
+"A 'startupProbe' indicates whether the application within the container is "
+"started.\n"
+"All other probes are disabled if a startup probe is provided, until it "
+"succeeds.\n"
+"If the startup probe fails, the container is subjected to its restart "
+"policy.\n"
+"\n"
+"A 'livenessProbe' is useful for applications running for long periods of "
+"time, eventually\n"
+"transitioning to broken states and cannot recover except by restarting "
+"them.\n"
+"\n"
+"Other times, a 'readinessProbe' is useful when the Container is only "
+"temporarily unable\n"
+"to serve, and will recover on its own. In this case, if a Container fails "
+"its 'readinessProbe'\n"
+", the Container will not be shut down, but rather the Container will stop "
+"receiving\n"
+"incoming requests.\n"
+"\n"
+"'httpGet' probes are just as it sounds: it performs a HTTP GET operation on "
+"the Container.\n"
+"A response code inside the 200-399 range is considered a pass. 'httpGet' "
+"probes accept a\n"
+"port number to perform the HTTP GET operation on the Container.\n"
+"\n"
+"'exec' probes run a command inside the Container to determine its health. An "
+"exit code of\n"
+"zero is considered a pass, while a non-zero status code is considered a "
+"fail. 'exec'\n"
+"probes accept a string of arguments to be run inside the Container.\n"
+"\n"
+"'tcpSocket' probes attempt to open a socket in the Container. The Container "
+"is only\n"
+"considered healthy if the check can establish a connection. 'tcpSocket' "
+"probes accept a\n"
+"port number to perform the socket connection on the Container.\n"
+msgstr ""
+
+#: internal/parser/labels.go:60
+msgid ""
+"Sets labels for an application.\n"
+"\n"
+"A label is a key/value pair used to label an application. This label is a "
+"general information for drycc user.\n"
+"Mostly used for administration/maintenance information, note for "
+"application. This information isn't send to scheduler."
+msgstr ""
+
+#: internal/parser/registry.go:76
+msgid ""
+"Sets registry information for an application. These credentials are the same "
+"as those used for\n"
+"'podmain login' to the private registry."
+msgstr ""
+
+#: internal/parser/limits.go:70
+msgid ""
+"Sets resource limits for an application.\n"
+"\n"
+"A resource limit is a finite resource within a pod which we can apply\n"
+"restrictions through Kubernetes."
+msgstr ""
+
+#: internal/parser/tags.go:87
+msgid ""
+"Sets tags for an application.\n"
+"\n"
+"A tag is a key/value pair used to tag an application's containers and is "
+"passed to the\n"
+"scheduler. This is often used to restrict workloads to specific hosts "
+"matching the\n"
+"scheduler-configured metadata."
+msgstr ""
+
+#: internal/parser/timeouts.go:65
+msgid "Sets termination grace period for an application"
+msgstr ""
+
+#: cmd/root.go:70
+msgid "Shortcut Commands:"
+msgstr ""
+
+#: internal/parser/certs.go:41
+msgid "Show certificate information for an SSL application"
+msgstr ""
+
+#: internal/parser/resources.go:159
+msgid "Show extra details provided to resource"
+msgstr ""
+
+#: internal/parser/update.go:20
+msgid "Simulate an update, only print the version info."
+msgstr ""
+
+#: internal/parser/apps.go:197
+msgid ""
+"Skips the prompt for the application name. \n"
+"<app> is the uniquely identifiable name for the application"
+msgstr ""
+
+#: internal/parser/apps.go:134 internal/parser/ps.go:76
+msgid "Specify if the logs should be streamed"
+msgstr ""
+
+#: internal/parser/resources.go:115 internal/parser/resources.go:199
+msgid "Specify values in a YAML file. If set, params will be discard"
+msgstr ""
+
+#: internal/parser/ps.go:113
+msgid "Stdin is a TTY"
+msgstr ""
+
+#: internal/parser/autoscale.go:74
+msgid "Target CPU utilization percentage"
+msgstr ""
+
+#: cmd/root.go:27
+msgid "The Drycc command-line client issues API calls to a Drycc controller"
+msgstr "Drycc命令行客户端向Drycc控制器发出API调用"
+
+#: internal/parser/healthchecks.go:181
+msgid "The HTTP headers to send for 'httpGet' probes, separated by commas"
+msgstr ""
+
+#: internal/parser/services.go:65 internal/parser/services.go:116
+msgid "The IP protocol for this port. Supports TCP, UDP, and SCTP"
+msgstr ""
+
+#: internal/parser/healthchecks.go:88
+msgid ""
+"The arguments required for the healthcheck probe. 'exec', accepts a list of "
+"arguments;\n"
+"                  'httpGet' and 'tcpSocket' accept a port number."
+msgstr ""
+
+#: internal/parser/volumes.go:211
+msgid "The client used to manage volume files"
+msgstr ""
+
+#: internal/parser/volumes.go:233 internal/parser/apps.go:158
+#: internal/parser/shortcuts.go:44
+msgid "The filesystem path"
+msgstr ""
+
+#: internal/parser/config.go:34 internal/parser/config.go:64
+msgid "The group for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:157
+msgid "The group for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/config.go:194
+msgid "The group for which the config needs to be push"
+msgstr ""
+
+#: internal/parser/config.go:93
+msgid "The group for which the config needs to be set"
+msgstr ""
+
+#: internal/parser/config.go:122
+msgid "The group for which the config needs to be unset"
+msgstr ""
+
+#: internal/parser/config.go:218
+msgid "The group that requires attach configurations"
+msgstr ""
+
+#: internal/parser/config.go:245
+msgid "The group that requires detach configurations"
+msgstr ""
+
+#: internal/parser/healthchecks.go:86
+msgid ""
+"The healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr ""
+
+#: internal/parser/volumes.go:85
+msgid "The hostname or ip address of the nfs server"
+msgstr ""
+
+#: internal/parser/tokens.go:86
+msgid "The id of the token for controller authentication"
+msgstr ""
+
+#: internal/parser/healthchecks.go:182
+msgid "The initial delay timeout for the probe"
+msgstr ""
+
+#: internal/parser/limits.go:66
+msgid "The limit plan id to apply to the process type"
+msgstr ""
+
+#: internal/parser/apps.go:135
+msgid "The max seconds of followz the log stream"
+msgstr ""
+
+#: internal/parser/certs.go:23 internal/parser/certs.go:48
+#: internal/parser/tokens.go:22 internal/parser/tokens.go:40
+#: internal/parser/users.go:21 internal/parser/users.go:39
+#: internal/parser/volumes.go:23 internal/parser/volumes.go:139
+#: internal/parser/perms.go:22 internal/parser/perms.go:44
+#: internal/parser/releases.go:33 internal/parser/releases.go:62
+#: internal/parser/routes.go:27 internal/parser/routes.go:107
+#: internal/parser/domains.go:22 internal/parser/domains.go:43
+#: internal/parser/gateways.go:22 internal/parser/gateways.go:44
+#: internal/parser/limits.go:126 internal/parser/limits.go:150
+#: internal/parser/resources.go:24 internal/parser/resources.go:51
+#: internal/parser/resources.go:76 internal/parser/resources.go:130
+msgid "The maximum number of results to display"
+msgstr ""
+
+#: internal/parser/certs.go:107
+msgid "The name of the cert to get information from"
+msgstr ""
+
+#: internal/parser/certs.go:85
+msgid "The name of the cert to remove from the app"
+msgstr ""
+
+#: internal/parser/perms.go:65 internal/parser/perms.go:95
+#: internal/parser/perms.go:123
+msgid "The name of the user"
+msgstr ""
+
+#: internal/parser/apps.go:133
+msgid "The number of lines to display"
+msgstr ""
+
+#: internal/parser/ps.go:75
+msgid "The number of lines to display, -1 showing all log lines"
+msgstr ""
+
+#: internal/parser/pts.go:120 internal/parser/shortcuts.go:83
+msgid "The number of processes"
+msgstr ""
+
+#: internal/parser/healthchecks.go:183
+msgid "The number of seconds after which the probe times out"
+msgstr ""
+
+#: internal/parser/registry.go:72
+msgid "The password of the registry"
+msgstr ""
+
+#: internal/parser/ps.go:99 internal/parser/ps.go:130 internal/parser/ps.go:152
+#: internal/parser/shortcuts.go:58
+msgid "The pod name for the application"
+msgstr ""
+
+#: internal/parser/services.go:100
+msgid "The port exposed by this service"
+msgstr ""
+
+#: internal/parser/services.go:51
+msgid "The port that will be exposed by this service"
+msgstr ""
+
+#: internal/parser/certs.go:61
+msgid "The private key of the SSL certificate"
+msgstr ""
+
+#: internal/parser/volumes.go:232 internal/parser/volumes.go:257
+#: internal/parser/pts.go:61 internal/parser/pts.go:119
+#: internal/parser/pts.go:153 internal/parser/shortcuts.go:82
+#: internal/parser/tags.go:26 internal/parser/tags.go:60
+#: internal/parser/tags.go:100 internal/parser/tags.go:130
+msgid "The process name as defined in your Procfile"
+msgstr ""
+
+#: internal/parser/releases.go:130
+msgid "The process name as defined in your Procfile, such as 'web'"
+msgstr ""
+
+#: internal/parser/timeouts.go:60 internal/parser/timeouts.go:86
+msgid "The process type as defined in your Procfile"
+msgstr ""
+
+#: internal/parser/limits.go:95
+msgid "The process type as defined in your Procfile, such as 'web' or 'worker'"
+msgstr ""
+
+#: internal/parser/limits.go:65
+msgid ""
+"The process type as defined in your Procfile, such as 'web' or 'worker'."
+msgstr ""
+
+#: internal/parser/autoscale.go:59
+msgid "The process type to add to the application's autoscale settings"
+msgstr ""
+
+#: internal/parser/autoscale.go:91
+msgid "The process type to remove from the application's autoscale settings"
+msgstr ""
+
+#: internal/parser/domains.go:62
+msgid "The ptype for domain"
+msgstr ""
+
+#: internal/parser/registry.go:26 internal/parser/registry.go:50
+#: internal/parser/registry.go:85 internal/parser/registry.go:106
+msgid "The ptype for registry"
+msgstr ""
+
+#: internal/parser/config.go:33 internal/parser/config.go:63
+msgid "The ptype for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:155
+msgid "The ptype for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/config.go:193
+msgid "The ptype for which the config needs to be push"
+msgstr ""
+
+#: internal/parser/config.go:92
+msgid "The ptype for which the config needs to be set"
+msgstr ""
+
+#: internal/parser/config.go:121
+msgid "The ptype for which the config needs to be unset"
+msgstr ""
+
+#: internal/parser/healthchecks.go:30
+msgid "The ptype for which the health check needs to be removed"
+msgstr ""
+
+#: internal/parser/config.go:217
+msgid "The ptype that requires attach configurations"
+msgstr ""
+
+#: internal/parser/config.go:244
+msgid "The ptype that requires detach configurations"
+msgstr ""
+
+#: internal/parser/certs.go:60
+msgid "The public key of the SSL certificate"
+msgstr ""
+
+#: internal/parser/healthchecks.go:180
+msgid "The relative URL path for 'httpGet' probes"
+msgstr ""
+
+#: internal/parser/releases.go:77 internal/parser/releases.go:131
+msgid "The release of the application, such as '1'"
+msgstr ""
+
+#: internal/parser/resources.go:217 internal/parser/resources.go:242
+#: internal/parser/resources.go:264
+msgid "The resource instance alias name"
+msgstr ""
+
+#: internal/parser/resources.go:97 internal/parser/resources.go:180
+msgid "The resource instance parameters key"
+msgstr ""
+
+#: internal/parser/resources.go:98 internal/parser/resources.go:181
+msgid "The resource instance parameters value"
+msgstr ""
+
+#: internal/parser/resources.go:95 internal/parser/resources.go:178
+msgid "The resource's service"
+msgstr ""
+
+#: internal/parser/routes.go:52
+msgid "The route kind, range: HTTPRoute,TCPRoute,UDPRoute,GRPCRoute,TLSRoute"
+msgstr ""
+
+#: internal/parser/routes.go:53
+msgid "The route's backend, pattern: <service>:<port>,<weight>"
+msgstr ""
+
+#: internal/parser/resources.go:64
+msgid "The service name for plans"
+msgstr ""
+
+#: internal/parser/resources.go:96 internal/parser/resources.go:179
+msgid "The service's plan"
+msgstr ""
+
+#: internal/parser/apps.go:159 internal/parser/shortcuts.go:45
+msgid "The shell command to run inside the container"
+msgstr ""
+
+#: internal/parser/builds.go:57
+msgid "The stack name for the application"
+msgstr ""
+
+#: internal/parser/tags.go:82
+msgid "The tag key"
+msgstr ""
+
+#: internal/parser/tags.go:118
+msgid "The tag key to unset"
+msgstr ""
+
+#: internal/parser/tags.go:83
+msgid "The tag value"
+msgstr ""
+
+#: internal/parser/routes.go:187 internal/parser/routes.go:219
+msgid "The unique name of the gaetway"
+msgstr ""
+
+#: internal/parser/routes.go:51 internal/parser/routes.go:120
+#: internal/parser/routes.go:146 internal/parser/routes.go:176
+#: internal/parser/routes.go:208 internal/parser/routes.go:237
+msgid "The unique name of the route"
+msgstr ""
+
+#: internal/parser/certs.go:22 internal/parser/routing.go:19
+#: internal/parser/services.go:23 internal/parser/timeouts.go:21
+#: internal/parser/volumes.go:22 internal/parser/apps.go:88
+#: internal/parser/apps.go:107 internal/parser/apps.go:132
+#: internal/parser/apps.go:170 internal/parser/apps.go:196
+#: internal/parser/apps.go:217 internal/parser/builds.go:19
+#: internal/parser/builds.go:56 internal/parser/ps.go:20
+#: internal/parser/ps.go:112 internal/parser/pts.go:20
+#: internal/parser/pts.go:133 internal/parser/perms.go:21
+#: internal/parser/releases.go:32 internal/parser/routes.go:26
+#: internal/parser/git.go:17 internal/parser/autodeploy.go:21
+#: internal/parser/autorollback.go:19 internal/parser/autoscale.go:21
+#: internal/parser/config.go:29 internal/parser/domains.go:21
+#: internal/parser/gateways.go:21 internal/parser/labels.go:20
+#: internal/parser/limits.go:20 internal/parser/registry.go:25
+#: internal/parser/resources.go:23 internal/parser/tags.go:25
+msgid "The uniquely identifiable name for the application"
+msgstr ""
+
+#: internal/parser/healthchecks.go:29
+msgid "The uniquely identifiable name of the application"
+msgstr ""
+
+#: internal/parser/perms.go:66
+msgid "The user permissions (view,change,delete)"
+msgstr ""
+
+#: internal/parser/perms.go:96
+msgid "The user's permissions (view,change,delete)"
+msgstr ""
+
+#: internal/parser/registry.go:71
+msgid "The username of the registry"
+msgstr ""
+
+#: internal/parser/users.go:78
+msgid "The username you want to disable"
+msgstr ""
+
+#: internal/parser/users.go:53
+msgid "The username you want to enable"
+msgstr ""
+
+#: internal/parser/timeouts.go:61
+msgid "The value to apply to the process type in seconds"
+msgstr ""
+
+#: internal/parser/builds.go:20 internal/parser/builds.go:79
+#: internal/parser/builds.go:104
+msgid "The version for which the build info needs to be displayed"
+msgstr ""
+
+#: internal/parser/config.go:35 internal/parser/config.go:65
+msgid "The version for which the config needs to be listed"
+msgstr ""
+
+#: internal/parser/config.go:156
+msgid "The version for which the config needs to be pull"
+msgstr ""
+
+#: internal/parser/healthchecks.go:31 internal/parser/healthchecks.go:56
+msgid "The version for which the health check needs to be listed"
+msgstr ""
+
+#: internal/parser/limits.go:21 internal/parser/limits.go:46
+msgid "The version for which the limit needs to be listed"
+msgstr ""
+
+#: internal/parser/registry.go:27 internal/parser/registry.go:51
+msgid "The version for which the registry needs to be listed"
+msgstr ""
+
+#: internal/parser/tags.go:27 internal/parser/tags.go:61
+msgid "The version for which the tag needs to be listed"
+msgstr ""
+
+#: internal/parser/timeouts.go:22 internal/parser/timeouts.go:44
+msgid "The version for which the timeout needs to be listed"
+msgstr ""
+
+#: internal/parser/volumes.go:57 internal/parser/volumes.go:112
+#: internal/parser/volumes.go:231 internal/parser/volumes.go:256
+#: internal/parser/apps.go:157 internal/parser/shortcuts.go:43
+msgid "The volume name"
+msgstr ""
+
+#: internal/parser/volumes.go:155
+msgid "The volume name to be info"
+msgstr ""
+
+#: internal/parser/volumes.go:181
+msgid "The volume name to be removed"
+msgstr ""
+
+#: internal/parser/volumes.go:58 internal/parser/volumes.go:113
+msgid "The volume size, such as '500G'"
+msgstr ""
+
+#: internal/parser/volumes.go:84
+msgid "The volume type, such as csi, nfs, oss"
+msgstr ""
+
+#: internal/parser/resources.go:94 internal/parser/resources.go:148
+#: internal/parser/resources.go:177
+msgid "This resource instance alias"
+msgstr ""
+
+#: internal/parser/tokens.go:98 internal/parser/builds.go:60
+#: internal/parser/builds.go:107 internal/parser/pts.go:98
+#: internal/parser/releases.go:118
+msgid "To proceed, type \"yes\""
+msgstr ""
+
+#: internal/parser/config.go:94 internal/parser/config.go:123
+#: internal/parser/config.go:196
+msgid "To proceed, type 'yes'"
+msgstr ""
+
+#: internal/parser/apps.go:209
+msgid "Transfer app ownership to another user"
+msgstr ""
+
+#: internal/parser/apps.go:210
+msgid "Transfer application ownership to another user."
+msgstr ""
+
+#: internal/parser/autoscale.go:95
+msgid "Turn off autoscale for an app"
+msgstr ""
+
+#: internal/parser/autoscale.go:63
+msgid "Turn on autoscale for an app"
+msgstr ""
+
+#: internal/parser/domains.go:74
+msgid "Unbinds a domain for an application"
+msgstr ""
+
+#: internal/parser/volumes.go:260
+msgid "Unmount a volume from process of the application"
+msgstr ""
+
+#: internal/parser/autoscale.go:96
+msgid "Unset autoscale per process type for an app"
+msgstr ""
+
+#: internal/parser/config.go:114
+msgid "Unset environment variables for an app"
+msgstr ""
+
+#: internal/parser/healthchecks.go:209
+msgid "Unset healthchecks for an application"
+msgstr ""
+
+#: internal/parser/registry.go:99
+msgid "Unset registry info for an app"
+msgstr ""
+
+#: internal/parser/limits.go:98
+msgid "Unset resource limits for an app"
+msgstr ""
+
+#: internal/parser/timeouts.go:89
+msgid "Unset resource timeouts for an app"
+msgstr ""
+
+#: internal/parser/tags.go:121
+msgid "Unset tags for an application"
+msgstr ""
+
+#: internal/parser/config.go:115
+msgid "Unsets an environment variable for an application or config group"
+msgstr ""
+
+#: internal/parser/labels.go:90
+msgid "Unsets labels for an application"
+msgstr ""
+
+#: internal/parser/registry.go:100
+msgid "Unsets registry information for an application"
+msgstr ""
+
+#: internal/parser/timeouts.go:90
+msgid ""
+"Unsets timeouts for an application. Default value (30s) or set by drycc "
+"controller"
+msgstr ""
+
+#: internal/parser/resources.go:184
+msgid "Update a resource from the application"
+msgstr ""
+
+#: internal/parser/perms.go:99
+msgid "Update a user permission"
+msgstr ""
+
+#: internal/parser/update.go:14 internal/parser/update.go:15
+msgid "Update workflow cli to latest release"
+msgstr ""
+
+#: internal/parser/tls.go:39
+msgid "View TLS settings information"
+msgstr ""
+
+#: internal/parser/routing.go:33
+msgid "View application's routability information"
+msgstr ""
+
+#: internal/parser/autorollback.go:33
+msgid "View autorollback info for an application"
+msgstr ""
+
+#: internal/parser/apps.go:81
+msgid "View info about an application"
+msgstr ""
+
+#: internal/parser/apps.go:171
+msgid "Volume mounts in format 'volume:path'"
+msgstr ""
+
+#: internal/parser/volumes.go:206
+msgid ""
+"arguments for running commands, when cmd is 'cp', args should be '[source] "
+"[dest]'.\n"
+"           volume path format 'vol://volumename/', '/' is equivalent to the "
+"mount path."
+msgstr ""
+
+#: internal/parser/builds.go:44
+msgid "imports an image and deploys as a new release"
+msgstr ""
+
+#: internal/parser/version.go:22
+msgid "list api and controller versions."
+msgstr ""
+
+#: internal/parser/volumes.go:203
+msgid ""
+"ls         list volume files\n"
+"           cp         copy volume files\n"
+"           rm         remove volume files"
+msgstr ""
+
+#: internal/parser/autoscale.go:72
+msgid "minimum replicas to keep around"
+msgstr ""
+
+#: internal/parser/services.go:99
+msgid "procfile type which should handle the request, e.g. webhooks"
+msgstr ""
+
+#: internal/parser/services.go:49
+msgid ""
+"procfile type which should handle the request, e.g. webhooks (should be bind "
+"to the port PORT)\n"
+"             only single extra service per Porcfile type could be created."
+msgstr ""
+
+#: internal/parser/volumes.go:91
+msgid "secret access key for authentication"
+msgstr ""
+
+#: internal/parser/resources.go:228
+msgid ""
+"skips the prompt for the resource name. <resource> is the uniquely "
+"identifiable\n"
+"name for the resource"
+msgstr ""
+
+#: internal/parser/healthchecks.go:87
+msgid "the healthcheck probe type, such as 'httpGet', 'exec' or 'tcpSocket'"
+msgstr ""
+
+#: internal/parser/healthchecks.go:206
+msgid ""
+"the healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr ""
+
+#: internal/parser/labels.go:84
+msgid "the label key to unset, for example: \"git_repo\" or \"team\""
+msgstr ""
+
+#: internal/parser/labels.go:55
+msgid "the label key, for example: \"git_repo\" or \"team\""
+msgstr ""
+
+#: internal/parser/labels.go:56
+msgid ""
+"the label value, for example: \"https://github.com/drycc/workflow\" or "
+"\"frontend\""
+msgstr ""
+
+#: internal/parser/resources.go:267
+msgid "unbind a resources for an application"
+msgstr ""
diff --git a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.mo b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.mo
new file mode 100644
index 0000000000000000000000000000000000000000..88d35c24a08740eba2de2f6d1bc9fdfa66060631
GIT binary patch
diff --git a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
new file mode 100644
index 000000000..ff1b0018b
--- /dev/null
+++ b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
@@ -0,0 +1,1818 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2025-03-31 14:12+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: internal/parser/builds.go:58 internal/parser/builds.go:105
+msgid "A YAML file used to supply a Procfile to the application"
+msgstr "用于向应用程序提供Procfile的YAML文件"
+
+#: internal/parser/keys.go:52
+msgid "A local file path to an SSH public key used to push application code"
+msgstr "用于推送应用程序代码的SSH公钥的本地文件路径"
+
+#: internal/parser/config.go:158 internal/parser/config.go:195
+msgid "A path leading to an environment file"
+msgstr "指向环境文件的路径"
+
+#: internal/parser/tls.go:121
+msgid "ACME account email"
+msgstr "ACME账户邮箱"
+
+#: internal/parser/tls.go:122
+msgid "ACME server URL"
+msgstr "ACME服务器URL"
+
+#: internal/parser/volumes.go:90
+msgid "Access key id for authentication"
+msgstr "用于身份验证的访问密钥ID"
+
+#: internal/parser/tls.go:55 internal/parser/tls.go:83
+msgid "Action to perform, 'enable' or 'disable'"
+msgstr "要执行的操作，'enable' 或 'disable'"
+
+#: internal/parser/tokens.go:58
+msgid "Add a token for controller authentication"
+msgstr "添加用于控制器身份验证的令牌"
+
+#: internal/parser/perms.go:69
+msgid "Add a user permissions"
+msgstr "添加用户权限"
+
+#: internal/parser/keys.go:56
+msgid "Add an SSH key"
+msgstr "添加SSH密钥"
+
+#: internal/parser/certs.go:65
+msgid "Add an SSL certificate to an app"
+msgstr "向应用添加SSL证书"
+
+#: internal/parser/labels.go:59
+msgid "Add new application's label"
+msgstr "添加新的应用程序标签"
+
+#: internal/parser/keys.go:57
+msgid "Adds SSH keys for the logged in user"
+msgstr "为登录用户添加SSH密钥"
+
+#: internal/parser/git.go:35
+msgid "Adds git remote of application to repository"
+msgstr "将应用程序的Git远程仓库添加到代码库"
+
+#: internal/parser/config.go:160
+msgid "Allows you to have the pull overwrite keys to the path"
+msgstr "允许将拉取的密钥覆盖到指定路径"
+
+# 完整翻译版本：
+#: internal/parser/config.go:57
+msgid "An app config info"
+msgstr "应用程序配置信息"
+
+#: internal/parser/tls.go:23
+msgid "Application name"
+msgstr "应用程序名称"
+
+#: internal/parser/certs.go:136
+msgid "Attach a certificate to a domain"
+msgstr "将证书附加到域名"
+
+#: internal/parser/certs.go:135
+msgid "Attach an SSL certificate to a domain"
+msgstr "将SSL证书附加到域名"
+
+#: internal/parser/routes.go:179
+msgid "Attach to gateway"
+msgstr "附加到网关"
+
+#: internal/parser/auth.go:34
+msgid "Authenticate against a controller"
+msgstr "对控制器进行身份验证"
+
+#: internal/parser/tls.go:88
+msgid "Automatic certificate management"
+msgstr "自动证书管理"
+
+#: internal/parser/domains.go:55
+msgid "Bind a domain to an application"
+msgstr "将域名绑定到应用程序"
+
+#: internal/parser/resources.go:245
+msgid "Bind a resource for an application"
+msgstr "为应用程序绑定资源"
+
+#: internal/parser/volumes.go:89
+msgid "Bucket name in object storage"
+msgstr "对象存储中的存储桶名称"
+
+#: internal/parser/tls.go:123
+msgid "CA key ID"
+msgstr "CA密钥ID"
+
+#: internal/parser/tls.go:124
+msgid "CA key secret"
+msgstr "CA密钥机密"
+
+#: internal/parser/pts.go:156
+msgid "Clean process types of not used"
+msgstr "清理未使用的进程类型"
+
+#: internal/parser/auth.go:53
+msgid "Clear the current user session"
+msgstr "清除当前用户会话"
+
+#: internal/parser/apps.go:172
+msgid "Command execution timeout in seconds"
+msgstr "命令执行超时时间（秒）"
+
+#: internal/parser/certs.go:131
+msgid ""
+"Common name of the domain to attach to (needs to already be in the system)"
+msgstr "要附加到的域名通用名称（需已存在于系统中）"
+
+#: internal/parser/certs.go:156
+msgid "Common name of the domain to detach from"
+msgstr "要解绑的域名通用名称"
+
+#: internal/parser/tls.go:115
+msgid "Configure automatic certificate management issuer details"
+msgstr "配置自动证书管理颁发者详情"
+
+#: internal/parser/apps.go:44
+msgid "Create a new application"
+msgstr "创建新应用"
+
+#: internal/parser/resources.go:101
+msgid "Create a resource for the application"
+msgstr "为应用创建资源"
+
+#: internal/parser/routes.go:56
+msgid "Create a route for an application"
+msgstr "创建应用路由"
+
+#: internal/parser/volumes.go:61
+msgid "Create a volume for the application"
+msgstr "创建应用卷"
+
+#: internal/parser/gateways.go:59
+msgid "Create gateways for an application"
+msgstr "创建应用网关"
+
+#: internal/parser/services.go:55
+msgid "Create service for an application"
+msgstr "创建应用服务"
+
+#: internal/parser/apps.go:45
+msgid ""
+"Creates a new application. If no <id> is provided, one will be generated "
+"automatically"
+msgstr "创建新应用。若未指定<id>，则自动生成一个唯一ID"
+
+#: internal/parser/builds.go:45
+msgid ""
+"Creates a new build of an application. Imports an <image> and deploys it to "
+"Drycc\n"
+"as a new release. If a Procfile or drycc.yaml is present in the current "
+"directory,\n"
+"it will be used as the default for this application."
+msgstr ""
+"创建应用新构建版本。导入<image>并部署到Drycc作为新版本\n"
+"若当前目录存在Procfile或drycc.yaml文件，\n"
+"将作为该应用的默认配置文件使用"
+
+#: internal/parser/services.go:56
+msgid ""
+"Creates extra service for an application and binds it to specific route of "
+"the main app domain"
+msgstr "为应用创建附加服务并绑定到主域名的特定路由"
+
+#: internal/parser/gateways.go:60
+msgid ""
+"Creates gateways for an application and binds it to allow listener of the "
+"main app domain"
+msgstr "为应用创建网关并绑定以监听主域名流量"
+
+#: internal/parser/routes.go:57
+msgid "Creates routes for an application, provides a way to route requests"
+msgstr "创建应用路由规则，实现请求分发"
+
+#: internal/parser/resources.go:220
+msgid "Delete a resource from the application"
+msgstr "从应用中删除资源"
+
+#: internal/parser/volumes.go:184
+msgid "Delete a volume from the application"
+msgstr "从应用中删除卷"
+
+#: internal/parser/ps.go:155
+msgid "Delete the selected processes"
+msgstr "删除选定进程"
+
+#: internal/parser/gateways.go:88
+msgid "Deletes specific gateway for application"
+msgstr "删除应用指定网关"
+
+#: internal/parser/releases.go:109
+msgid "Deploy the latest release by process types"
+msgstr "按进程类型部署最新版本"
+
+#: internal/parser/pts.go:64
+msgid "Describe a process type"
+msgstr "描述进程类型定义"
+
+#: internal/parser/apps.go:190
+msgid "Destroy an application"
+msgstr "销毁应用"
+
+#: internal/parser/certs.go:160
+msgid "Detach an SSL certificate from a domain"
+msgstr "从域名解绑SSL证书"
+
+#: internal/parser/certs.go:161
+msgid "Detach certificate from a domain"
+msgstr "从域名解绑证书"
+
+#: internal/parser/routes.go:211
+msgid "Dettach to gateway"
+msgstr "从网关解绑"
+
+#: internal/parser/users.go:81
+msgid "Disable a user"
+msgstr "禁用用户"
+
+#: internal/parser/users.go:82
+msgid ""
+"Disable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+"禁用用户账户（需管理员权限）\n"
+"仅当用户状态为启用时生效"
+
+#: internal/parser/autorollback.go:58
+msgid "Disable autorollback for an application"
+msgstr "禁用应用自动回滚"
+
+#: internal/parser/routing.go:57
+msgid "Disable routing for an application"
+msgstr "禁用应用路由功能"
+
+#: internal/parser/autodeploy.go:59
+msgid "Disables autodeploy for an app"
+msgstr "禁用应用自动部署"
+
+#: cmd/root.go:34 internal/parser/version.go:16
+msgid "Display client version"
+msgstr "显示客户端版本"
+
+#: cmd/root.go:35
+msgid "Display help information"
+msgstr "显示帮助信息"
+
+#: internal/parser/auth.go:71
+msgid "Display the current user"
+msgstr "显示当前登录用户"
+
+#: internal/parser/version.go:17
+msgid "Displays the client version"
+msgstr "显示客户端版本信息"
+
+#: internal/parser/auth.go:72
+msgid "Displays the currently logged in user"
+msgstr "显示当前登录用户信息"
+
+#: internal/parser/apps.go:56
+msgid "Do not create a 'drycc' git remote"
+msgstr "不创建drycc Git远程仓库"
+
+#: internal/parser/builds.go:59 internal/parser/builds.go:106
+msgid "Drycc config path to the application"
+msgstr "Drycc应用配置路径"
+
+#: internal/parser/users.go:56
+msgid "Enable a user"
+msgstr "启用用户"
+
+#: internal/parser/users.go:57
+msgid ""
+"Enable a user when his status is disabled.\n"
+"Requires admin privileges."
+msgstr ""
+"启用已禁用用户（需管理员权限）\n"
+"仅当用户状态为禁用时生效"
+
+#: internal/parser/autorollback.go:46
+msgid "Enable autorollback for an application"
+msgstr "启用应用自动回滚"
+
+#: internal/parser/routing.go:45
+msgid "Enable routing for an application"
+msgstr "启用应用路由功能"
+
+#: internal/parser/autodeploy.go:47
+msgid "Enables autodeploy for an app"
+msgstr "启用应用自动部署"
+
+#: internal/parser/auth.go:43
+msgid "Enables or disables SSL certificate verification for API requests"
+msgstr "启用/禁用API请求的SSL证书验证"
+
+#: internal/parser/volumes.go:88
+msgid "Endpoint url for object storage service"
+msgstr "对象存储服务端点URL"
+
+#: internal/parser/ps.go:102
+msgid "Execute a command in a container"
+msgstr "在容器中执行命令"
+
+#: internal/parser/volumes.go:116
+msgid "Expand a volume capacity for the application"
+msgstr "扩展应用卷容量"
+
+#: internal/parser/config.go:145
+msgid ""
+"Extract all environment variables from an application or config group. for "
+"local use.\n"
+"\n"
+"The environmental variables can be piped into a file, 'drycc config:pull > "
+"file',\n"
+"or stored locally in a file named .env. This file can be\n"
+"read by foreman to load the local environment for your app."
+msgstr ""
+"导出应用或配置组的环境变量（用于本地使用）\n"
+"\n"
+"可通过管道输出到文件：drycc config:pull > 文件名，\n"
+"或保存到本地.env文件。该文件可\n"
+"被Foreman读取以加载本地应用环境"
+
+#: internal/parser/auth.go:78
+msgid "Fetch detailed user information"
+msgstr "获取用户详细信息"
+
+#: internal/parser/certs.go:112
+msgid "Fetch more detailed information about a certificate"
+msgstr "获取证书详细信息"
+
+#: internal/parser/tls.go:60
+msgid "Force TLS settings to HTTPS-only redirection"
+msgstr "强制TLS设置为HTTPS重定向"
+
+#: internal/parser/releases.go:117
+msgid "Force deploy"
+msgstr "强制部署"
+
+#: internal/parser/resources.go:151
+msgid "Get a resource's detail in the application"
+msgstr "获取应用资源详情"
+
+#: internal/parser/certs.go:111
+msgid "Get detailed informaton about the certificate"
+msgstr "获取证书详细信息"
+
+#: internal/parser/routes.go:123
+msgid "Get route rules"
+msgstr "获取路由规则"
+
+#: internal/parser/routes.go:124
+msgid "Get route rules for an application"
+msgstr "获取应用路由规则"
+
+#: internal/parser/healthchecks.go:184
+msgid "How often (in seconds) to perform the probe"
+msgstr "探测执行频率（秒）"
+
+#: internal/parser/keys.go:29
+msgid "List SSH keys for the logged in user"
+msgstr "列出登录用户的SSH密钥"
+
+#: internal/parser/certs.go:40
+msgid "List SSL certificates for an application"
+msgstr "列出应用SSL证书"
+
+#: internal/parser/apps.go:65
+msgid "List accessible applications"
+msgstr "列出可访问应用"
+
+#: internal/parser/limits.go:140
+msgid "List all available limit plans"
+msgstr "列出所有可用配额方案"
+
+#: internal/parser/limits.go:118
+msgid "List all available limit specs"
+msgstr "列出所有可用配额规格"
+
+#: internal/parser/resources.go:67
+msgid "List all available plans for a resource service"
+msgstr "列出资源服务所有可用方案"
+
+#: internal/parser/resources.go:44
+msgid "List all available resource services"
+msgstr "列出所有资源服务"
+
+#: internal/parser/users.go:32
+msgid "List all registered users"
+msgstr "列出所有注册用户"
+
+#: internal/parser/perms.go:37
+msgid "List all user permissions"
+msgstr "列出所有用户权限"
+
+#: internal/parser/releases.go:53
+msgid "List an application's release history"
+msgstr "列出应用版本历史"
+
+#: internal/parser/gateways.go:36
+msgid "List application gateways"
+msgstr "列出应用网关"
+
+#: internal/parser/ps.go:37
+msgid "List application pods"
+msgstr "列出应用Pod"
+
+#: internal/parser/pts.go:37
+msgid "List application process types"
+msgstr "列出应用进程类型"
+
+#: internal/parser/routes.go:100
+msgid "List application routes"
+msgstr "列出应用路由"
+
+#: internal/parser/services.go:76
+msgid "List application services"
+msgstr "列出应用服务"
+
+#: internal/parser/labels.go:33
+msgid "List application's labels"
+msgstr "列出应用标签"
+
+#: internal/parser/autoscale.go:34
+msgid "List autoscale options for an application"
+msgstr "列出应用自动扩缩容选项"
+
+#: internal/parser/domains.go:36
+msgid "List domains bound to an application"
+msgstr "列出绑定到应用的域名"
+
+#: internal/parser/healthchecks.go:50
+msgid "List healthchecks for an application"
+msgstr "列出应用健康检查配置"
+
+#: internal/parser/registry.go:43
+msgid "List registry info for an app"
+msgstr "列出应用镜像仓库信息"
+
+#: internal/parser/limits.go:40
+msgid "List resource limits for an app"
+msgstr "列出应用资源配额"
+
+#: internal/parser/timeouts.go:38
+msgid "List resource timeouts for an app"
+msgstr "列出应用资源超时设置"
+
+#: internal/parser/resources.go:123
+msgid "List resources in the application"
+msgstr "列出应用资源"
+
+#: internal/parser/limits.go:117
+msgid "List specification information of the server"
+msgstr "列出服务器规格信息"
+
+#: internal/parser/tags.go:52
+msgid "List tags for an application"
+msgstr "列出应用标签"
+
+#: internal/parser/volumes.go:132
+msgid "List volumes in the application"
+msgstr "列出应用卷"
+
+#: internal/parser/apps.go:66
+msgid "Lists applications visible to the current user"
+msgstr "列出当前用户可见的应用"
+
+#: internal/parser/gateways.go:37
+msgid "Lists gateways for an application"
+msgstr "列出应用网关"
+
+#: internal/parser/pts.go:38
+msgid "Lists process types servicing an application"
+msgstr "列出应用进程类型"
+
+#: internal/parser/ps.go:38
+msgid "Lists processes servicing an application"
+msgstr "列出应用运行进程"
+
+#: internal/parser/registry.go:44
+msgid "Lists registry information for an application"
+msgstr "列出应用镜像仓库信息"
+
+#: internal/parser/releases.go:54
+msgid "Lists release history for an application"
+msgstr "列出应用版本历史记录"
+
+#: internal/parser/tokens.go:33
+msgid "Lists tokens visible to the current controller"
+msgstr "列出控制器可见的令牌"
+
+#: internal/parser/auth.go:35
+msgid "Logs in by authenticating against a controller"
+msgstr "通过控制器身份验证登录"
+
+#: internal/parser/auth.go:54
+msgid "Logs out from a controller and clears the user session"
+msgstr "退出控制器并清除用户会话"
+
+#: internal/parser/certs.go:15
+msgid "Manage SSL endpoints for an app"
+msgstr "管理应用SSL端点"
+
+#: internal/parser/tls.go:17
+msgid "Manage TLS/SSL settings for applications"
+msgstr "管理应用TLS/SSL设置"
+
+#: internal/parser/domains.go:14
+msgid "Manage and assign domain names to your applications"
+msgstr "管理并分配域名到应用"
+
+#: internal/parser/healthchecks.go:23
+msgid "Manage application healthchecks"
+msgstr "管理应用健康检查"
+
+#: internal/parser/apps.go:17
+msgid "Manage applications used to provide services"
+msgstr "管理服务型应用"
+
+#: internal/parser/auth.go:13
+msgid "Manage authentication"
+msgstr "管理身份验证"
+
+#: internal/parser/autodeploy.go:15
+msgid "Manage autodeploy if or not for applications"
+msgstr "管理应用自动部署开关"
+
+#: internal/parser/autorollback.go:14
+msgid "Manage autorollback if or not for application"
+msgstr "管理应用自动回滚开关"
+
+#: internal/parser/autoscale.go:15
+msgid "Manage autoscale for applications"
+msgstr "管理应用自动扩缩容"
+
+#: internal/parser/builds.go:13
+msgid "Manage builds created using 'git push'"
+msgstr "管理通过git push创建的构建版本"
+
+#: internal/parser/config.go:23
+msgid "Manage environment variables that define app config"
+msgstr "管理定义应用配置的环境变量"
+
+#: internal/parser/gateways.go:14
+msgid "Manage gateways for your applications"
+msgstr "管理应用网关"
+
+#: internal/parser/git.go:13
+msgid "Manage git for applications"
+msgstr "管理应用Git配置"
+
+#: internal/parser/labels.go:14
+msgid "Manage labels of application"
+msgstr "管理应用标签"
+
+#: internal/parser/perms.go:14
+msgid "Manage permissions for applications"
+msgstr "管理应用权限"
+
+#: internal/parser/timeouts.go:15
+msgid "Manage pods termination grace period"
+msgstr "管理Pod终止宽限期"
+
+#: internal/parser/registry.go:19
+msgid "Manage private registry information for your application"
+msgstr "管理应用私有镜像仓库信息"
+
+#: internal/parser/pts.go:14
+msgid "Manage process types inside an app"
+msgstr "管理应用进程类型"
+
+#: internal/parser/ps.go:14
+msgid "Manage processes inside an app"
+msgstr "管理应用进程"
+
+#: internal/parser/users.go:14
+msgid "Manage registered users"
+msgstr "管理注册用户"
+
+#: internal/parser/releases.go:24
+msgid "Manage releases of an application"
+msgstr "管理应用版本"
+
+#: internal/parser/limits.go:14
+msgid "Manage resource limits for your application"
+msgstr "管理应用资源配额"
+
+#: internal/parser/resources.go:16
+msgid "Manage resources for your applications"
+msgstr "管理应用资源"
+
+#: internal/parser/routing.go:13
+msgid "Manage routability of an application"
+msgstr "管理应用路由能力"
+
+#: internal/parser/routes.go:19
+msgid "Manage routes for your applications"
+msgstr "管理应用路由规则"
+
+#: internal/parser/services.go:17
+msgid "Manage services for your applications"
+msgstr "管理应用服务"
+
+#: internal/parser/keys.go:14
+msgid "Manage ssh keys used for 'git push' deployments"
+msgstr "管理用于git push部署的SSH密钥"
+
+#: internal/parser/tags.go:19
+msgid "Manage tags for application containers"
+msgstr "管理应用容器标签"
+
+#: internal/parser/tokens.go:15
+msgid "Manage user tokens"
+msgstr "管理用户令牌"
+
+#: internal/parser/volumes.go:15
+msgid "Manage volumes for your applications"
+msgstr "管理应用卷"
+
+#: internal/parser/autoscale.go:73
+msgid "Max replicas to scale up to"
+msgstr "可扩展的最大副本数"
+
+#: internal/parser/apps.go:22 internal/parser/apps.go:72
+msgid "Maximum number of results to display"
+msgstr "最多显示结果数量"
+
+#: internal/parser/healthchecks.go:186
+msgid ""
+"Minimum consecutive successes for the probe to be considered failed after "
+"having succeeded"
+msgstr "探测成功后需连续失败的最小次数（判定为失败）"
+
+#: internal/parser/healthchecks.go:185
+msgid ""
+"Minimum consecutive successes for the probe to be considered successful "
+"after having failed"
+msgstr "探测失败后需连续成功的最小次数（判定为成功）"
+
+#: internal/parser/volumes.go:236
+msgid "Mount a volume to process of the application"
+msgstr "将卷挂载到应用进程"
+
+#: internal/parser/apps.go:55 internal/parser/git.go:41
+msgid "Name of remote to create"
+msgstr "要创建的远程名称"
+
+#: internal/parser/keys.go:51
+msgid "Name of the SSH key"
+msgstr "SSH密钥名称"
+
+#: internal/parser/certs.go:130
+msgid "Name of the certificate to attach domain to"
+msgstr "要附加域名的证书名称"
+
+#: internal/parser/certs.go:155
+msgid "Name of the certificate to deatch from a domain"
+msgstr "要解绑域名的证书名称"
+
+#: internal/parser/certs.go:59
+msgid "Name of the certificate to reference it by"
+msgstr "证书引用名称"
+
+#: internal/parser/services.go:52
+msgid ""
+"Number or name of the port to access on the pods targeted by the service"
+msgstr "服务目标Pod上要访问的端口号或名称"
+
+#: internal/parser/apps.go:100
+msgid "Open the application in a browser"
+msgstr "在浏览器中打开应用"
+
+#: internal/parser/apps.go:101
+msgid "Opens a URL to the application in the default browser"
+msgstr "通过默认浏览器打开应用URL"
+
+#: internal/parser/git.go:42
+msgid "Overwrite remote of the given name if it already exists"
+msgstr "若远程名称已存在则覆盖"
+
+#: internal/parser/ps.go:114
+msgid "Pass stdin to the container"
+msgstr "将标准输入传递给容器"
+
+#: internal/parser/volumes.go:86
+msgid "Path that is exported by the nfs server"
+msgstr "NFS服务器导出的路径"
+
+#: cmd/root.go:33
+msgid "Path to configuration file"
+msgstr "配置文件的路径"
+
+#: internal/parser/routes.go:188 internal/parser/routes.go:220
+msgid "Port is the network port, the gateway listener expects to receive"
+msgstr "网关监听器期望接收的网络端口"
+
+#: internal/parser/gateways.go:67
+msgid "Port is the network port, the listener expects to receive"
+msgstr "网关监听器期望接收的网络端口"
+
+#: internal/parser/gateways.go:96
+msgid "Port is the network port, the listener received"
+msgstr "网关监听器接收的网络端口"
+
+#: internal/parser/ps.go:133
+msgid "Print a detailed description of the selected process"
+msgstr "显示所选进程的详细信息"
+
+#: internal/parser/pts.go:65
+msgid "Print a detailed description of the selected process type"
+msgstr "显示所选进程类型的详细信息"
+
+#: internal/parser/builds.go:74
+msgid "Print information about a specific build"
+msgstr "显示特定构建版本信息"
+
+#: internal/parser/releases.go:80
+msgid "Print information about a specific release"
+msgstr "显示特定版本信息"
+
+#: internal/parser/volumes.go:158
+msgid "Print information about a volume"
+msgstr "显示卷信息"
+
+#: internal/parser/builds.go:99
+msgid "Print process info about a specific build"
+msgstr "显示特定构建版本的进程信息"
+
+#: internal/parser/ps.go:63
+msgid "Print the logs for a container"
+msgstr "显示容器日志"
+
+#: internal/parser/ps.go:64
+msgid "Print the logs for a container in a pod or specified resource"
+msgstr "显示Pod或指定资源中的容器日志"
+
+#: internal/parser/ps.go:78
+msgid ""
+"Print the logs for the previous instance of the container in a pod if it "
+"exists"
+msgstr "若存在则显示Pod中容器的前一个实例日志"
+
+#: internal/parser/ps.go:77
+msgid "Print the logs of this container"
+msgstr "显示该容器的日志"
+
+#: internal/parser/autoscale.go:35
+msgid "Prints a list of autoscale options for the application"
+msgstr "显示应用自动扩缩容选项列表"
+
+#: internal/parser/labels.go:34
+msgid "Prints a list of labels of the application"
+msgstr "显示应用标签列表"
+
+#: internal/parser/releases.go:81
+msgid "Prints info about a particular release"
+msgstr "显示特定版本信息"
+
+#: internal/parser/apps.go:82
+msgid "Prints info about the current application"
+msgstr "显示当前应用信息"
+
+#: internal/parser/autodeploy.go:35
+msgid "Prints info about the current application's autodeploy if or not"
+msgstr "显示当前应用是否启用自动部署"
+
+#: internal/parser/autorollback.go:34
+msgid "Prints info about the current application's autorollback if or not"
+msgstr "显示当前应用是否启用自动回滚"
+
+#: internal/parser/config.go:159
+msgid "Prompts for each value to be overwritten"
+msgstr "逐项提示覆盖值"
+
+#: internal/parser/gateways.go:68
+msgid ""
+"Protocol specifies the network protocol this listener expects to receive. "
+"Supports TCP, UDP, TLS, HTTP, and HTTPS"
+msgstr "网关监听器期望接收的网络协议（支持TCP/UDP/TLS/HTTP/HTTPS）"
+
+#: internal/parser/gateways.go:97
+msgid "Protocol specifies the network protocol this listener received"
+msgstr "网关监听器接收的网络协议"
+
+#: internal/parser/tokens.go:55
+msgid "Provide a alias for controller authentication token"
+msgstr "提供控制器认证令牌别名"
+
+#: internal/parser/tokens.go:68 internal/parser/auth.go:42
+msgid "Provide a password for the account"
+msgstr "提供账户密码"
+
+#: internal/parser/tokens.go:67 internal/parser/auth.go:41
+msgid "Provide a username for the account"
+msgstr "提供账户用户名"
+
+#: internal/parser/config.go:144
+msgid "Pull environment variables to the path"
+msgstr "将环境变量拉取到指定路径"
+
+#: internal/parser/config.go:182
+msgid "Push environment variables from the path"
+msgstr "从指定路径推送环境变量"
+
+#: internal/parser/limits.go:148
+msgid "Query plans that meet the specified memory capacity, unit GiB"
+msgstr "查询符合指定内存容量（单位GiB）的配额方案"
+
+#: internal/parser/limits.go:147
+msgid "Query plans that meet the specified number of cpu cores"
+msgstr "查询符合指定CPU核心数的配额方案"
+
+#: internal/parser/limits.go:149
+msgid "Query plans that meet the specified spec id, see [specs] subcommand."
+msgstr "查询符合指定规格ID的配额方案，使用[specs]子命令查看可用规格"
+
+#: internal/parser/routes.go:240
+msgid "Remove a route from an application"
+msgstr "从应用移除路由"
+
+#: internal/parser/tokens.go:89
+msgid "Remove a token for controller authentication"
+msgstr "移除控制器认证令牌"
+
+#: internal/parser/perms.go:126
+msgid "Remove a user permissions"
+msgstr "移除用户权限"
+
+#: internal/parser/keys.go:80
+msgid "Remove an SSH key"
+msgstr "移除SSH密钥"
+
+#: internal/parser/certs.go:89
+msgid "Remove an SSL certificate from an app"
+msgstr "从应用移除SSL证书"
+
+#: internal/parser/labels.go:89
+msgid "Remove application's label"
+msgstr "移除应用标签"
+
+#: internal/parser/gateways.go:87
+msgid "Remove gateways from an application"
+msgstr "从应用移除网关"
+
+#: internal/parser/services.go:103
+msgid "Remove service from an application"
+msgstr "从应用移除服务"
+
+#: internal/parser/keys.go:81
+msgid "Removes an SSH key for the logged in user"
+msgstr "移除登录用户的SSH密钥"
+
+#: internal/parser/git.go:51
+msgid "Removes git remote of application from repository"
+msgstr "从代码库移除应用的Git远程仓库"
+
+#: internal/parser/pts.go:90
+msgid "Restart an application or process types"
+msgstr "重启应用或进程类型"
+
+#: internal/parser/pts.go:89
+msgid "Restart application process types"
+msgstr "重启应用进程类型"
+
+#: internal/parser/apps.go:173
+msgid "Retention time of records in seconds"
+msgstr "日志记录保留时间（秒）"
+
+#: internal/parser/apps.go:125
+msgid "Retrieve application log events"
+msgstr "获取应用日志事件"
+
+#: internal/parser/apps.go:126
+msgid "Retrieves the most recent log events"
+msgstr "获取最近日志事件"
+
+#: internal/parser/releases.go:134
+msgid "Return to a previous release"
+msgstr "回滚到之前版本"
+
+#: internal/parser/releases.go:135
+msgid "Rolls back to a previous application release"
+msgstr "回滚到应用之前的版本"
+
+#: internal/parser/routes.go:158
+msgid "Rules-file is the file name of rule to apply"
+msgstr "要应用的规则文件名"
+
+#: internal/parser/apps.go:162
+msgid "Run a command in an ephemeral app container"
+msgstr "在临时应用容器中运行命令"
+
+#: internal/parser/apps.go:163
+msgid "Runs a command inside an ephemeral app container"
+msgstr "在临时应用容器内部运行命令"
+
+#: internal/parser/builds.go:108
+msgid "Save process info to the local"
+msgstr "将进程信息保存到本地"
+
+#: internal/parser/pts.go:123
+msgid "Scale process types of replicas"
+msgstr "缩放进程类型的副本数"
+
+#: internal/parser/pts.go:124
+msgid "Scales an application's processes by type"
+msgstr "按类型缩放应用进程"
+
+#: internal/parser/limits.go:125
+msgid ""
+"Search keywords separated by commas, matching must satisfy all of the "
+"specified."
+msgstr "以逗号分隔的搜索关键词，需满足所有指定条件"
+
+#: internal/parser/config.go:223
+msgid "Selects environment groups to attach an app ptype"
+msgstr "选择要附加配置组的环境组"
+
+#: internal/parser/config.go:248
+msgid "Selects environment groups to detach an app ptype"
+msgstr "选择要分离配置组的环境组"
+
+#: internal/parser/autoscale.go:64
+msgid "Set autoscale option per process type for an app"
+msgstr "设置应用各进程类型的自动扩缩容选项"
+
+#: internal/parser/config.go:85
+msgid "Set environment variables for an app"
+msgstr "设置应用环境变量"
+
+#: internal/parser/healthchecks.go:92
+msgid "Set healthchecks for an application"
+msgstr "设置应用健康检查配置"
+
+#: internal/parser/registry.go:75
+msgid "Set registry info for an app"
+msgstr "设置应用镜像仓库信息"
+
+#: internal/parser/limits.go:69
+msgid "Set resource limits for an app"
+msgstr "设置应用资源配额"
+
+#: internal/parser/timeouts.go:64
+msgid "Set resource timeouts for an app"
+msgstr "设置应用资源超时设置"
+
+#: internal/parser/routes.go:149
+msgid "Set route rules"
+msgstr "设置路由规则"
+
+#: internal/parser/routes.go:150
+msgid "Set route rules for an application"
+msgstr "设置应用路由规则"
+
+#: internal/parser/tags.go:86
+msgid "Set tags for an application"
+msgstr "设置应用标签"
+
+#: internal/parser/config.go:86
+msgid "Sets environment variables for an application or config group"
+msgstr "设置应用或配置组的环境变量"
+
+#: internal/parser/config.go:183
+msgid ""
+"Sets environment variables for an application or config group.\n"
+"\n"
+"This file can be read by foreman\n"
+"to load the local environment for your app. The file should be piped via\n"
+"stdin, 'drycc config:push < .env', or using the --path option."
+msgstr ""
+"设置应用或配置组的环境变量。\n"
+"\n"
+"该文件可被Foreman读取以加载本地环境。可通过管道输入：\n"
+"drycc config:push < .env，或使用--path选项指定路径"
+
+#: internal/parser/healthchecks.go:93
+msgid ""
+"Sets healthchecks for an application.\n"
+"\n"
+"By default, Workflow only checks that the application starts in their "
+"Container. A health\n"
+"check may be added by configuring a health check probe for the application. "
+"The health\n"
+"checks are implemented as Kubernetes Container Probes. A 'startupProbe' "
+"'livenessProbe' \n"
+"and a 'readinessProbe' can be configured, and each probe can be of type "
+"'httpGet', 'exec' \n"
+"or 'tcpSocket' depending on the type of probe the Container requires.\n"
+"\n"
+"A 'startupProbe' indicates whether the application within the container is "
+"started.\n"
+"All other probes are disabled if a startup probe is provided, until it "
+"succeeds.\n"
+"If the startup probe fails, the container is subjected to its restart "
+"policy.\n"
+"\n"
+"A 'livenessProbe' is useful for applications running for long periods of "
+"time, eventually\n"
+"transitioning to broken states and cannot recover except by restarting "
+"them.\n"
+"\n"
+"Other times, a 'readinessProbe' is useful when the Container is only "
+"temporarily unable\n"
+"to serve, and will recover on its own. In this case, if a Container fails "
+"its 'readinessProbe'\n"
+", the Container will not be shut down, but rather the Container will stop "
+"receiving\n"
+"incoming requests.\n"
+"\n"
+"'httpGet' probes are just as it sounds: it performs a HTTP GET operation on "
+"the Container.\n"
+"A response code inside the 200-399 range is considered a pass. 'httpGet' "
+"probes accept a\n"
+"port number to perform the HTTP GET operation on the Container.\n"
+"\n"
+"'exec' probes run a command inside the Container to determine its health. An "
+"exit code of\n"
+"zero is considered a pass, while a non-zero status code is considered a "
+"fail. 'exec'\n"
+"probes accept a string of arguments to be run inside the Container.\n"
+"\n"
+"'tcpSocket' probes attempt to open a socket in the Container. The Container "
+"is only\n"
+"considered healthy if the check can establish a connection. 'tcpSocket' "
+"probes accept a\n"
+"port number to perform the socket connection on the Container.\n"
+msgstr ""
+"Sets healthchecks for an application.\n"
+"\n"
+"By default, Workflow only checks that the application starts in their "
+"Container. A health\n"
+"check may be added by configuring a health check probe for the application. "
+"The health\n"
+"checks are implemented as Kubernetes Container Probes. A 'startupProbe' "
+"'livenessProbe' \n"
+"and a 'readinessProbe' can be configured, and each probe can be of type "
+"'httpGet', 'exec' \n"
+"or 'tcpSocket' depending on the type of probe the Container requires.\n"
+"\n"
+"A 'startupProbe' indicates whether the application within the container is "
+"started.\n"
+"All other probes are disabled if a startup probe is provided, until it "
+"succeeds.\n"
+"If the startup probe fails, the container is subjected to its restart "
+"policy.\n"
+"\n"
+"A 'livenessProbe' is useful for applications running for long periods of "
+"time, eventually\n"
+"transitioning to broken states and cannot recover except by restarting "
+"them.\n"
+"\n"
+"Other times, a 'readinessProbe' is useful when the Container is only "
+"temporarily unable\n"
+"to serve, and will recover on its own. In this case, if a Container fails "
+"its 'readinessProbe'\n"
+", the Container will not be shut down, but rather the Container will stop "
+"receiving\n"
+"incoming requests.\n"
+"\n"
+"'httpGet' probes are just as it sounds: it performs a HTTP GET operation on "
+"the Container.\n"
+"A response code inside the 200-399 range is considered a pass. 'httpGet' "
+"probes accept a\n"
+"port number to perform the HTTP GET operation on the Container.\n"
+"\n"
+"'exec' probes run a command inside the Container to determine its health. An "
+"exit code of\n"
+"zero is considered a pass, while a non-zero status code is considered a "
+"fail. 'exec'\n"
+"probes accept a string of arguments to be run inside the Container.\n"
+"\n"
+"'tcpSocket' probes attempt to open a socket in the Container. The Container "
+"is only\n"
+"considered healthy if the check can establish a connection. 'tcpSocket' "
+"probes accept a\n"
+"port number to perform the socket connection on the Container.\n"
+
+#: internal/parser/labels.go:60
+msgid ""
+"Sets labels for an application.\n"
+"\n"
+"A label is a key/value pair used to label an application. This label is a "
+"general information for drycc user.\n"
+"Mostly used for administration/maintenance information, note for "
+"application. This information isn't send to scheduler."
+msgstr ""
+"设置应用标签。\n"
+"\n"
+"标签是键值对形式的元数据，用于标识应用。这些标签属于Drycc用户通用信息，主要用"
+"于管理/维护备注，不会发送给调度器。"
+
+#: internal/parser/registry.go:76
+msgid ""
+"Sets registry information for an application. These credentials are the same "
+"as those used for\n"
+"'podmain login' to the private registry."
+msgstr ""
+"Sets registry information for an application. These credentials are the same "
+"as those used for\n"
+"'podmain login' to the private registry."
+
+#: internal/parser/limits.go:70
+msgid ""
+"Sets resource limits for an application.\n"
+"\n"
+"A resource limit is a finite resource within a pod which we can apply\n"
+"restrictions through Kubernetes."
+msgstr ""
+"设置应用资源配额。\n"
+"\n"
+"资源配额是Pod内有限资源的约束，通过Kubernetes进行限制。"
+
+#: internal/parser/tags.go:87
+msgid ""
+"Sets tags for an application.\n"
+"\n"
+"A tag is a key/value pair used to tag an application's containers and is "
+"passed to the\n"
+"scheduler. This is often used to restrict workloads to specific hosts "
+"matching the\n"
+"scheduler-configured metadata."
+msgstr ""
+"设置应用容器标签。\n"
+"\n"
+"标签是键值对形式的元数据，用于标记应用容器并传递给调度器。常用于将工作负载限"
+"制到特定主机（需与调度器元数据匹配）。"
+
+#: internal/parser/timeouts.go:65
+msgid "Sets termination grace period for an application"
+msgstr "设置应用终止宽限期"
+
+#: cmd/root.go:70
+msgid "Shortcut Commands:"
+msgstr "Shortcut Commands:"
+
+#: internal/parser/certs.go:41
+msgid "Show certificate information for an SSL application"
+msgstr "显示SSL应用证书信息"
+
+#: internal/parser/resources.go:159
+msgid "Show extra details provided to resource"
+msgstr "显示资源附加详情"
+
+#: internal/parser/update.go:20
+msgid "Simulate an update, only print the version info."
+msgstr "模拟更新，仅显示版本信息"
+
+#: internal/parser/apps.go:197
+msgid ""
+"Skips the prompt for the application name. \n"
+"<app> is the uniquely identifiable name for the application"
+msgstr ""
+"跳过应用名称提示，直接使用指定名称。\n"
+"<app> 是应用的唯一标识名称"
+
+#: internal/parser/apps.go:134 internal/parser/ps.go:76
+msgid "Specify if the logs should be streamed"
+msgstr "指定是否实时流式输出日志"
+
+#: internal/parser/resources.go:115 internal/parser/resources.go:199
+msgid "Specify values in a YAML file. If set, params will be discard"
+msgstr "通过YAML文件指定参数值，若设置则忽略命令行参数"
+
+#: internal/parser/ps.go:113
+msgid "Stdin is a TTY"
+msgstr "标准输入为TTY终端"
+
+#: internal/parser/autoscale.go:74
+msgid "Target CPU utilization percentage"
+msgstr "目标CPU使用率百分比"
+
+#: cmd/root.go:27
+msgid "The Drycc command-line client issues API calls to a Drycc controller"
+msgstr "Drycc命令行客户端向Drycc控制器发出API调用"
+
+#: internal/parser/healthchecks.go:181
+msgid "The HTTP headers to send for 'httpGet' probes, separated by commas"
+msgstr "HTTP探测时发送的HTTP头部字段（逗号分隔）"
+
+#: internal/parser/services.go:65 internal/parser/services.go:116
+msgid "The IP protocol for this port. Supports TCP, UDP, and SCTP"
+msgstr "该端口的IP协议（支持TCP/UDP/SCTP）"
+
+#: internal/parser/healthchecks.go:88
+msgid ""
+"The arguments required for the healthcheck probe. 'exec', accepts a list of "
+"arguments;\n"
+"                  'httpGet' and 'tcpSocket' accept a port number."
+msgstr ""
+"健康检查探测所需的参数：\n"
+"                  exec探测接受参数列表，httpGet和tcpSocket探测需指定端口号"
+
+#: internal/parser/volumes.go:211
+msgid "The client used to manage volume files"
+msgstr "用于管理卷文件的客户端"
+
+#: internal/parser/volumes.go:233 internal/parser/apps.go:158
+#: internal/parser/shortcuts.go:44
+msgid "The filesystem path"
+msgstr "文件系统路径"
+
+#: internal/parser/config.go:34 internal/parser/config.go:64
+msgid "The group for which the config needs to be listed"
+msgstr "要列出配置的组"
+
+#: internal/parser/config.go:157
+msgid "The group for which the config needs to be pull"
+msgstr "要拉取配置的组"
+
+#: internal/parser/config.go:194
+msgid "The group for which the config needs to be push"
+msgstr "要推送配置的组"
+
+#: internal/parser/config.go:93
+msgid "The group for which the config needs to be set"
+msgstr "要设置配置的组"
+
+#: internal/parser/config.go:122
+msgid "The group for which the config needs to be unset"
+msgstr "要清除配置的组"
+
+#: internal/parser/config.go:218
+msgid "The group that requires attach configurations"
+msgstr "需附加配置的组"
+
+#: internal/parser/config.go:245
+msgid "The group that requires detach configurations"
+msgstr "需分离配置的组"
+
+#: internal/parser/healthchecks.go:86
+msgid ""
+"The healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr "健康检查类型，如 'startupProbe'、'livenessProbe' 或 'readinessProbe'"
+
+#: internal/parser/volumes.go:85
+msgid "The hostname or ip address of the nfs server"
+msgstr "NFS服务器的主机名或IP地址"
+
+#: internal/parser/tokens.go:86
+msgid "The id of the token for controller authentication"
+msgstr "控制器认证的令牌ID"
+
+#: internal/parser/healthchecks.go:182
+msgid "The initial delay timeout for the probe"
+msgstr "探测初始延迟超时时间"
+
+#: internal/parser/limits.go:66
+msgid "The limit plan id to apply to the process type"
+msgstr "要应用到进程类型的配额方案ID"
+
+#: internal/parser/apps.go:135
+msgid "The max seconds of followz the log stream"
+msgstr "日志流实时追踪的最大秒数"
+
+#: internal/parser/certs.go:23 internal/parser/certs.go:48
+#: internal/parser/tokens.go:22 internal/parser/tokens.go:40
+#: internal/parser/users.go:21 internal/parser/users.go:39
+#: internal/parser/volumes.go:23 internal/parser/volumes.go:139
+#: internal/parser/perms.go:22 internal/parser/perms.go:44
+#: internal/parser/releases.go:33 internal/parser/releases.go:62
+#: internal/parser/routes.go:27 internal/parser/routes.go:107
+#: internal/parser/domains.go:22 internal/parser/domains.go:43
+#: internal/parser/gateways.go:22 internal/parser/gateways.go:44
+#: internal/parser/limits.go:126 internal/parser/limits.go:150
+#: internal/parser/resources.go:24 internal/parser/resources.go:51
+#: internal/parser/resources.go:76 internal/parser/resources.go:130
+msgid "The maximum number of results to display"
+msgstr "最多显示结果数量"
+
+#: internal/parser/certs.go:107
+msgid "The name of the cert to get information from"
+msgstr "要查询信息的证书名称"
+
+#: internal/parser/certs.go:85
+msgid "The name of the cert to remove from the app"
+msgstr "要从应用移除的证书名称"
+
+#: internal/parser/perms.go:65 internal/parser/perms.go:95
+#: internal/parser/perms.go:123
+msgid "The name of the user"
+msgstr "用户名称"
+
+#: internal/parser/apps.go:133
+msgid "The number of lines to display"
+msgstr "显示行数"
+
+#: internal/parser/ps.go:75
+msgid "The number of lines to display, -1 showing all log lines"
+msgstr "要显示的日志行数（-1 表示显示全部）"
+
+#: internal/parser/pts.go:120 internal/parser/shortcuts.go:83
+msgid "The number of processes"
+msgstr "进程数量"
+
+#: internal/parser/healthchecks.go:183
+msgid "The number of seconds after which the probe times out"
+msgstr "探测超时秒数"
+
+#: internal/parser/registry.go:72
+msgid "The password of the registry"
+msgstr "镜像仓库密码"
+
+#: internal/parser/ps.go:99 internal/parser/ps.go:130 internal/parser/ps.go:152
+#: internal/parser/shortcuts.go:58
+msgid "The pod name for the application"
+msgstr "应用Pod名称"
+
+#: internal/parser/services.go:100
+msgid "The port exposed by this service"
+msgstr "服务暴露的端口"
+
+#: internal/parser/services.go:51
+msgid "The port that will be exposed by this service"
+msgstr "服务将暴露的端口"
+
+#: internal/parser/certs.go:61
+msgid "The private key of the SSL certificate"
+msgstr "SSL证书私钥"
+
+#: internal/parser/volumes.go:232 internal/parser/volumes.go:257
+#: internal/parser/pts.go:61 internal/parser/pts.go:119
+#: internal/parser/pts.go:153 internal/parser/shortcuts.go:82
+#: internal/parser/tags.go:26 internal/parser/tags.go:60
+#: internal/parser/tags.go:100 internal/parser/tags.go:130
+msgid "The process name as defined in your Procfile"
+msgstr "Procfile中定义的进程名称"
+
+#: internal/parser/releases.go:130
+msgid "The process name as defined in your Procfile, such as 'web'"
+msgstr "Procfile中定义的进程名称（如 'web'）"
+
+#: internal/parser/timeouts.go:60 internal/parser/timeouts.go:86
+msgid "The process type as defined in your Procfile"
+msgstr "Procfile中定义的进程类型"
+
+#: internal/parser/limits.go:95
+msgid "The process type as defined in your Procfile, such as 'web' or 'worker'"
+msgstr ""
+"The process type as defined in your Procfile, such as 'web' or 'worker'"
+
+#: internal/parser/limits.go:65
+msgid ""
+"The process type as defined in your Procfile, such as 'web' or 'worker'."
+msgstr "Procfile中定义的进程类型（如 'web' 或 'worker'）"
+
+#: internal/parser/autoscale.go:59
+msgid "The process type to add to the application's autoscale settings"
+msgstr "要添加到应用自动扩缩容设置的进程类型"
+
+#: internal/parser/autoscale.go:91
+msgid "The process type to remove from the application's autoscale settings"
+msgstr "要从应用自动扩缩容设置移除的进程类型"
+
+#: internal/parser/domains.go:62
+msgid "The ptype for domain"
+msgstr "域名对应的配置进程类型"
+
+#: internal/parser/registry.go:26 internal/parser/registry.go:50
+#: internal/parser/registry.go:85 internal/parser/registry.go:106
+msgid "The ptype for registry"
+msgstr "镜像仓库对应的配置进程类型"
+
+#: internal/parser/config.go:33 internal/parser/config.go:63
+msgid "The ptype for which the config needs to be listed"
+msgstr "要列出配置的进程类型"
+
+#: internal/parser/config.go:155
+msgid "The ptype for which the config needs to be pull"
+msgstr "要拉取配置的进程类型"
+
+#: internal/parser/config.go:193
+msgid "The ptype for which the config needs to be push"
+msgstr "要推送配置的进程类型"
+
+#: internal/parser/config.go:92
+msgid "The ptype for which the config needs to be set"
+msgstr "要设置配置的进程类型"
+
+#: internal/parser/config.go:121
+msgid "The ptype for which the config needs to be unset"
+msgstr "要清除配置的进程类型"
+
+#: internal/parser/healthchecks.go:30
+msgid "The ptype for which the health check needs to be removed"
+msgstr "要移除健康检查的进程类型"
+
+#: internal/parser/config.go:217
+msgid "The ptype that requires attach configurations"
+msgstr "需附加配置的进程类型"
+
+#: internal/parser/config.go:244
+msgid "The ptype that requires detach configurations"
+msgstr "需分离配置的进程类型"
+
+#: internal/parser/certs.go:60
+msgid "The public key of the SSL certificate"
+msgstr "SSL证书公钥"
+
+#: internal/parser/healthchecks.go:180
+msgid "The relative URL path for 'httpGet' probes"
+msgstr "HTTP探测的相对URL路径"
+
+#: internal/parser/releases.go:77 internal/parser/releases.go:131
+msgid "The release of the application, such as '1'"
+msgstr "应用版本（如 '1'）"
+
+#: internal/parser/resources.go:217 internal/parser/resources.go:242
+#: internal/parser/resources.go:264
+msgid "The resource instance alias name"
+msgstr "资源实例别名"
+
+#: internal/parser/resources.go:97 internal/parser/resources.go:180
+msgid "The resource instance parameters key"
+msgstr "资源实例参数键"
+
+#: internal/parser/resources.go:98 internal/parser/resources.go:181
+msgid "The resource instance parameters value"
+msgstr "资源实例参数值"
+
+#: internal/parser/resources.go:95 internal/parser/resources.go:178
+msgid "The resource's service"
+msgstr "资源所属服务"
+
+#: internal/parser/routes.go:52
+msgid "The route kind, range: HTTPRoute,TCPRoute,UDPRoute,GRPCRoute,TLSRoute"
+msgstr "路由类型（可选：HTTPRoute、TCPRoute、UDPRoute、GRPCRoute、TLSRoute）"
+
+#: internal/parser/routes.go:53
+msgid "The route's backend, pattern: <service>:<port>,<weight>"
+msgstr "路由后端配置，格式：<service>:<port>,<weight>"
+
+#: internal/parser/resources.go:64
+msgid "The service name for plans"
+msgstr "服务名称"
+
+#: internal/parser/resources.go:96 internal/parser/resources.go:179
+msgid "The service's plan"
+msgstr "服务配额方案"
+
+#: internal/parser/apps.go:159 internal/parser/shortcuts.go:45
+msgid "The shell command to run inside the container"
+msgstr "容器内要执行的Shell命令"
+
+#: internal/parser/builds.go:57
+msgid "The stack name for the application"
+msgstr "应用堆栈名称"
+
+#: internal/parser/tags.go:82
+msgid "The tag key"
+msgstr "标签键"
+
+#: internal/parser/tags.go:118
+msgid "The tag key to unset"
+msgstr "要清除的标签键"
+
+#: internal/parser/tags.go:83
+msgid "The tag value"
+msgstr "标签值"
+
+#: internal/parser/routes.go:187 internal/parser/routes.go:219
+msgid "The unique name of the gaetway"
+msgstr "网关唯一名称"
+
+#: internal/parser/routes.go:51 internal/parser/routes.go:120
+#: internal/parser/routes.go:146 internal/parser/routes.go:176
+#: internal/parser/routes.go:208 internal/parser/routes.go:237
+msgid "The unique name of the route"
+msgstr "路由唯一名称"
+
+#: internal/parser/certs.go:22 internal/parser/routing.go:19
+#: internal/parser/services.go:23 internal/parser/timeouts.go:21
+#: internal/parser/volumes.go:22 internal/parser/apps.go:88
+#: internal/parser/apps.go:107 internal/parser/apps.go:132
+#: internal/parser/apps.go:170 internal/parser/apps.go:196
+#: internal/parser/apps.go:217 internal/parser/builds.go:19
+#: internal/parser/builds.go:56 internal/parser/ps.go:20
+#: internal/parser/ps.go:112 internal/parser/pts.go:20
+#: internal/parser/pts.go:133 internal/parser/perms.go:21
+#: internal/parser/releases.go:32 internal/parser/routes.go:26
+#: internal/parser/git.go:17 internal/parser/autodeploy.go:21
+#: internal/parser/autorollback.go:19 internal/parser/autoscale.go:21
+#: internal/parser/config.go:29 internal/parser/domains.go:21
+#: internal/parser/gateways.go:21 internal/parser/labels.go:20
+#: internal/parser/limits.go:20 internal/parser/registry.go:25
+#: internal/parser/resources.go:23 internal/parser/tags.go:25
+msgid "The uniquely identifiable name for the application"
+msgstr "应用唯一标识名称"
+
+#: internal/parser/healthchecks.go:29
+msgid "The uniquely identifiable name of the application"
+msgstr "应用唯一名称"
+
+#: internal/parser/perms.go:66
+msgid "The user permissions (view,change,delete)"
+msgstr "用户权限（view、change、delete）"
+
+#: internal/parser/perms.go:96
+msgid "The user's permissions (view,change,delete)"
+msgstr "用户的权限（view、change、delete）"
+
+#: internal/parser/registry.go:71
+msgid "The username of the registry"
+msgstr "镜像仓库用户名"
+
+#: internal/parser/users.go:78
+msgid "The username you want to disable"
+msgstr "要禁用的用户名"
+
+#: internal/parser/users.go:53
+msgid "The username you want to enable"
+msgstr "要启用的用户名"
+
+#: internal/parser/timeouts.go:61
+msgid "The value to apply to the process type in seconds"
+msgstr "要应用到进程类型的秒数值"
+
+#: internal/parser/builds.go:20 internal/parser/builds.go:79
+#: internal/parser/builds.go:104
+msgid "The version for which the build info needs to be displayed"
+msgstr "要查询对应版本的构建信息"
+
+#: internal/parser/config.go:35 internal/parser/config.go:65
+msgid "The version for which the config needs to be listed"
+msgstr "要查询对应版本的配置"
+
+#: internal/parser/config.go:156
+msgid "The version for which the config needs to be pull"
+msgstr "要拉取对应版本的配置"
+
+#: internal/parser/healthchecks.go:31 internal/parser/healthchecks.go:56
+msgid "The version for which the health check needs to be listed"
+msgstr "要查询对应版本的健康检查"
+
+#: internal/parser/limits.go:21 internal/parser/limits.go:46
+msgid "The version for which the limit needs to be listed"
+msgstr "要查询对应版本配额"
+
+#: internal/parser/registry.go:27 internal/parser/registry.go:51
+msgid "The version for which the registry needs to be listed"
+msgstr "要查询对应版本的镜像仓库信息"
+
+#: internal/parser/tags.go:27 internal/parser/tags.go:61
+msgid "The version for which the tag needs to be listed"
+msgstr "要查询对应版本的标签"
+
+#: internal/parser/timeouts.go:22 internal/parser/timeouts.go:44
+msgid "The version for which the timeout needs to be listed"
+msgstr "要查询对应版本的超时设置"
+
+#: internal/parser/volumes.go:57 internal/parser/volumes.go:112
+#: internal/parser/volumes.go:231 internal/parser/volumes.go:256
+#: internal/parser/apps.go:157 internal/parser/shortcuts.go:43
+msgid "The volume name"
+msgstr "卷名称"
+
+#: internal/parser/volumes.go:155
+msgid "The volume name to be info"
+msgstr "要查询信息的卷名称"
+
+#: internal/parser/volumes.go:181
+msgid "The volume name to be removed"
+msgstr "要移除的卷名称"
+
+#: internal/parser/volumes.go:58 internal/parser/volumes.go:113
+msgid "The volume size, such as '500G'"
+msgstr "卷容量（如 '500G'）"
+
+#: internal/parser/volumes.go:84
+msgid "The volume type, such as csi, nfs, oss"
+msgstr "卷类型（如 csi, nfs, oss）"
+
+#: internal/parser/resources.go:94 internal/parser/resources.go:148
+#: internal/parser/resources.go:177
+msgid "This resource instance alias"
+msgstr "该资源实例别名"
+
+#: internal/parser/tokens.go:98 internal/parser/builds.go:60
+#: internal/parser/builds.go:107 internal/parser/pts.go:98
+#: internal/parser/releases.go:118
+msgid "To proceed, type \"yes\""
+msgstr "继续操作请输入 \"yes\""
+
+#: internal/parser/config.go:94 internal/parser/config.go:123
+#: internal/parser/config.go:196
+msgid "To proceed, type 'yes'"
+msgstr "继续操作请输入 'yes'"
+
+#: internal/parser/apps.go:209
+msgid "Transfer app ownership to another user"
+msgstr "转移应用所有权给其他用户"
+
+#: internal/parser/apps.go:210
+msgid "Transfer application ownership to another user."
+msgstr "转移应用所有权给其他用户"
+
+#: internal/parser/autoscale.go:95
+msgid "Turn off autoscale for an app"
+msgstr "关闭应用自动扩缩容"
+
+#: internal/parser/autoscale.go:63
+msgid "Turn on autoscale for an app"
+msgstr "开启应用自动扩缩容"
+
+#: internal/parser/domains.go:74
+msgid "Unbinds a domain for an application"
+msgstr "解除应用与域名的绑定"
+
+#: internal/parser/volumes.go:260
+msgid "Unmount a volume from process of the application"
+msgstr "从应用进程卸载卷"
+
+#: internal/parser/autoscale.go:96
+msgid "Unset autoscale per process type for an app"
+msgstr "清除应用各进程类型的自动扩缩容设置"
+
+#: internal/parser/config.go:114
+msgid "Unset environment variables for an app"
+msgstr "清除应用环境变量"
+
+#: internal/parser/healthchecks.go:209
+msgid "Unset healthchecks for an application"
+msgstr "清除应用健康检查配置"
+
+#: internal/parser/registry.go:99
+msgid "Unset registry info for an app"
+msgstr "清除应用镜像仓库信息"
+
+#: internal/parser/limits.go:98
+msgid "Unset resource limits for an app"
+msgstr "清除应用资源配额"
+
+#: internal/parser/timeouts.go:89
+msgid "Unset resource timeouts for an app"
+msgstr "清除应用资源超时设置"
+
+#: internal/parser/tags.go:121
+msgid "Unset tags for an application"
+msgstr "清除应用标签"
+
+#: internal/parser/config.go:115
+msgid "Unsets an environment variable for an application or config group"
+msgstr "清除应用或配置组的环境变量"
+
+#: internal/parser/labels.go:90
+msgid "Unsets labels for an application"
+msgstr "清除应用标签"
+
+#: internal/parser/registry.go:100
+msgid "Unsets registry information for an application"
+msgstr "清除应用镜像仓库信息"
+
+#: internal/parser/timeouts.go:90
+msgid ""
+"Unsets timeouts for an application. Default value (30s) or set by drycc "
+"controller"
+msgstr "清除应用超时设置（将恢复默认值30秒或控制器设定值）"
+
+#: internal/parser/resources.go:184
+msgid "Update a resource from the application"
+msgstr "更新应用资源"
+
+#: internal/parser/perms.go:99
+msgid "Update a user permission"
+msgstr "更新用户权限"
+
+#: internal/parser/update.go:14 internal/parser/update.go:15
+msgid "Update workflow cli to latest release"
+msgstr "更新Workflow CLI到最新版本"
+
+#: internal/parser/tls.go:39
+msgid "View TLS settings information"
+msgstr "查看TLS设置信息"
+
+#: internal/parser/routing.go:33
+msgid "View application's routability information"
+msgstr "查看应用路由能力信息"
+
+#: internal/parser/autorollback.go:33
+msgid "View autorollback info for an application"
+msgstr "查看应用自动回滚信息"
+
+#: internal/parser/apps.go:81
+msgid "View info about an application"
+msgstr "查看应用信息"
+
+#: internal/parser/apps.go:171
+msgid "Volume mounts in format 'volume:path'"
+msgstr "卷挂载格式 '卷名:挂载路径'"
+
+#: internal/parser/volumes.go:206
+msgid ""
+"arguments for running commands, when cmd is 'cp', args should be '[source] "
+"[dest]'.\n"
+"           volume path format 'vol://volumename/', '/' is equivalent to the "
+"mount path."
+msgstr ""
+"执行命令的参数，当命令为'cp'时，参数应为'[源路径] [目标路径]'\n"
+"           卷路径格式为'vol://卷名称/'，'/'等同于挂载路径"
+
+#: internal/parser/builds.go:44
+msgid "imports an image and deploys as a new release"
+msgstr "导入镜像并部署为新版本"
+
+#: internal/parser/version.go:22
+msgid "list api and controller versions."
+msgstr "列出API和控制器版本"
+
+#: internal/parser/volumes.go:203
+msgid ""
+"ls         list volume files\n"
+"           cp         copy volume files\n"
+"           rm         remove volume files"
+msgstr ""
+"ls         列出卷文件\n"
+"           cp         复制卷文件\n"
+"           rm         删除卷文件"
+
+#: internal/parser/autoscale.go:72
+msgid "minimum replicas to keep around"
+msgstr "保留的最小副本数"
+
+#: internal/parser/services.go:99
+msgid "procfile type which should handle the request, e.g. webhooks"
+msgstr "要处理请求的Procfile类型（如webhooks）"
+
+#: internal/parser/services.go:49
+msgid ""
+"procfile type which should handle the request, e.g. webhooks (should be bind "
+"to the port PORT)\n"
+"             only single extra service per Porcfile type could be created."
+msgstr ""
+"要处理请求的Procfile类型（如webhooks），需绑定到PORT端口\n"
+"             每个Procfile类型只能创建一个附加服务"
+
+#: internal/parser/volumes.go:91
+msgid "secret access key for authentication"
+msgstr "认证密钥（Secret Access Key）"
+
+#: internal/parser/resources.go:228
+msgid ""
+"skips the prompt for the resource name. <resource> is the uniquely "
+"identifiable\n"
+"name for the resource"
+msgstr "跳过资源名称提示，直接使用指定名称。<resource>是资源的唯一标识名称"
+
+#: internal/parser/healthchecks.go:87
+msgid "the healthcheck probe type, such as 'httpGet', 'exec' or 'tcpSocket'"
+msgstr "健康检查探测类型（如 'httpGet'、'exec' 或 'tcpSocket'）"
+
+#: internal/parser/healthchecks.go:206
+msgid ""
+"the healthcheck type, such as 'startupProbe' 'livenessProbe' or "
+"'readinessProbe'"
+msgstr "健康检查类型，如 'startupProbe'、'livenessProbe' 或 'readinessProbe'"
+
+#: internal/parser/labels.go:84
+msgid "the label key to unset, for example: \"git_repo\" or \"team\""
+msgstr "要清除的标签键（例如：\"git_repo\" 或 \"team\"）"
+
+#: internal/parser/labels.go:55
+msgid "the label key, for example: \"git_repo\" or \"team\""
+msgstr "标签键（例如：\"git_repo\" 或 \"team\"）"
+
+#: internal/parser/labels.go:56
+msgid ""
+"the label value, for example: \"https://github.com/drycc/workflow\" or "
+"\"frontend\""
+msgstr "标签值（例如：\"https://github.com/drycc/workflow\" 或 \"frontend\"）"
+
+#: internal/parser/resources.go:267
+msgid "unbind a resources for an application"
+msgstr "解除应用与资源的绑定"
+
+#~ msgid ""
+#~ "Protocol specifies the network protocol this listener expects to receive"
+#~ msgstr "网关监听器期望接收的网络协议"
+
+#~ msgid ""
+#~ "procfile type which should handle the request, e.g. webhooks (should be "
+#~ "bind to the port PORT)\n"
+#~ msgstr "要处理请求的Procfile类型（如webhooks），需绑定到PORT端口"
diff --git a/pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.mo b/pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.mo
new file mode 100644
index 0000000000000000000000000000000000000000..5487fb8ccf9de73999585748dd9b9ed6ce09d34c
GIT binary patch
diff --git a/pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.po b/pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.po
new file mode 100644
index 000000000..7e77c4300
--- /dev/null
+++ b/pkg/i18n/translations/test/en_US/LC_MESSAGES/cli.po
@@ -0,0 +1,28 @@
+# Test translations for unit tests.
+# Copyright (C) 2016
+# This file is distributed under the same license as the Kubernetes package.
+# FIRST AUTHOR brendan.d.burns@gmail.com, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gettext-go-examples-hello\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-12-12 20:03+0000\n"
+"PO-Revision-Date: 2016-12-13 21:35-0800\n"
+"Last-Translator: Brendan Burns <brendan.d.burns@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.6.10\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"Language-Team: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: en\n"
+
+msgid "test_plural"
+msgid_plural "test_plural"
+msgstr[0] "there was %d item"
+msgstr[1] "there were %d items"
+
+msgid "test_string"
+msgstr "foo"
diff --git a/pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.mo b/pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.mo
new file mode 100644
index 0000000000000000000000000000000000000000..44fe7b5d964a89516db4c6379aa5fb87a941e120
GIT binary patch
diff --git a/pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.po b/pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.po
new file mode 100644
index 000000000..9944046ae
--- /dev/null
+++ b/pkg/i18n/translations/test/zh_CN/LC_MESSAGES/cli.po
@@ -0,0 +1,28 @@
+# Test translations for unit tests.
+# Copyright (C) 2016
+# This file is distributed under the same license as the Kubernetes package.
+# FIRST AUTHOR brendan.d.burns@gmail.com, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gettext-go-examples-hello\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-12-12 20:03+0000\n"
+"PO-Revision-Date: 2016-12-13 22:12-0800\n"
+"Last-Translator: Brendan Burns <brendan.d.burns@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.6.10\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"Language-Team: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language: en\n"
+
+msgid "test_plural"
+msgid_plural "test_plural"
+msgstr[0] "there was %d item"
+msgstr[1] "there were %d items"
+
+msgid "test_string"
+msgstr "baz"
diff --git a/settings/home_unix.go b/pkg/settings/home_unix.go
similarity index 100%
rename from settings/home_unix.go
rename to pkg/settings/home_unix.go
diff --git a/settings/home_unix_test.go b/pkg/settings/home_unix_test.go
similarity index 100%
rename from settings/home_unix_test.go
rename to pkg/settings/home_unix_test.go
diff --git a/settings/home_windows.go b/pkg/settings/home_windows.go
similarity index 100%
rename from settings/home_windows.go
rename to pkg/settings/home_windows.go
diff --git a/settings/home_windows_test.go b/pkg/settings/home_windows_test.go
similarity index 100%
rename from settings/home_windows_test.go
rename to pkg/settings/home_windows_test.go
diff --git a/settings/settings.go b/pkg/settings/settings.go
similarity index 88%
rename from settings/settings.go
rename to pkg/settings/settings.go
index 60ac34eab..031add317 100644
--- a/settings/settings.go
+++ b/pkg/settings/settings.go
@@ -84,20 +84,28 @@ func (s *Settings) Save(cf string) (string, error) {
 		Controller: s.Client.ControllerURL.String(), Token: s.Client.Token, Limit: s.Limit}
 
 	settingsContents, err := json.Marshal(settings)
-
 	if err != nil {
 		return "", err
 	}
 
-	if err = os.MkdirAll(filepath.Join(FindHome(), "/.drycc/"), 0700); err != nil {
-		return "", err
-	}
-
 	filename := locateSettingsFile(cf)
 
 	return filename, os.WriteFile(filename, settingsContents, 0600)
 }
 
+// DryccHome returns the path to the user's settings path.
+func DryccHome() string {
+	dryccHome := filepath.Join(FindHome(), "/.drycc/")
+	_, err := os.Stat(dryccHome)
+	if os.IsNotExist(err) {
+		if err := os.MkdirAll(dryccHome, 0700); err != nil {
+			panic(err)
+		}
+		return dryccHome
+	}
+	return dryccHome
+}
+
 // Delete user's settings file.
 func Delete(cf string) error {
 	filename := locateSettingsFile(cf)
diff --git a/settings/settings_test.go b/pkg/settings/settings_test.go
similarity index 100%
rename from settings/settings_test.go
rename to pkg/settings/settings_test.go
diff --git a/settings/utils.go b/pkg/settings/utils.go
similarity index 58%
rename from settings/utils.go
rename to pkg/settings/utils.go
index a7f4cc44b..1a9104fa9 100644
--- a/settings/utils.go
+++ b/pkg/settings/utils.go
@@ -4,11 +4,22 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"strings"
 )
 
 var filepathRegex = regexp.MustCompile(`^.*[/\\].+\.json$`)
 
 func locateSettingsFile(cf string) string {
+	if strings.HasPrefix(cf, "~") {
+		homeDir, err := os.UserHomeDir()
+		if err != nil {
+			panic(err)
+		}
+		cf = homeDir + cf[1:]
+	}
+
+	cf = os.ExpandEnv(cf)
+
 	if cf == "" {
 		if v, ok := os.LookupEnv("DRYCC_PROFILE"); ok {
 			cf = v
@@ -17,10 +28,11 @@ func locateSettingsFile(cf string) string {
 		}
 	}
 
-	// if path appears to be a filepath (contains a separator and ends in .json) don't alter the path
+	cf = filepath.Clean(cf)
+
 	if filepathRegex.MatchString(cf) {
 		return cf
 	}
 
-	return filepath.Join(FindHome(), ".drycc", cf+".json")
+	return filepath.Join(DryccHome(), cf+".json")
 }
diff --git a/settings/utils_test.go b/pkg/settings/utils_test.go
similarity index 92%
rename from settings/utils_test.go
rename to pkg/settings/utils_test.go
index 93a91abc3..4081b7293 100644
--- a/settings/utils_test.go
+++ b/pkg/settings/utils_test.go
@@ -18,7 +18,7 @@ func TestSelectSettings(t *testing.T) {
 	cases := []confgCases{
 		{"test", filepath.Join(FindHome(), ".drycc", "test.json")},
 		{"", filepath.Join(FindHome(), ".drycc", "client.json")},
-		{"~/test.json", "~/test.json"},
+		{"~/test.json", filepath.Join(FindHome(), "test.json")},
 		{"/opt/test.json", "/opt/test.json"},
 	}
 
diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go
index 41bd6fb79..a1367dff1 100644
--- a/pkg/testutil/testutil.go
+++ b/pkg/testutil/testutil.go
@@ -11,7 +11,7 @@ import (
 	"testing"
 
 	drycc "github.com/drycc/controller-sdk-go"
-	"github.com/drycc/workflow-cli/settings"
+	"github.com/drycc/workflow-cli/pkg/settings"
 )
 
 // TestServer represents a test HTTP server along with a path to a config file
diff --git a/scripts/update-translations.sh b/scripts/update-translations.sh
new file mode 100755
index 000000000..883740930
--- /dev/null
+++ b/scripts/update-translations.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+
+# Copyright 2017 The Kubernetes Authors.
+#
+# 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.
+
+# This script updates `pkg/i18n/translations/drycc/template.pot` and
+# generates/fixes .po and .mo files.
+# Usage: `update-translations.sh`.
+
+
+# Opposite of ensure-temp-dir()
+cleanup-temp-dir() {
+  rm -rf "${WORKFLOW_CLI_TEMP}"
+}
+
+# Create a temp dir that'll be deleted at the end of this bash session.
+#
+# Vars set:
+#   WORKFLOW_CLI_TEMP
+ensure-temp-dir() {
+  if [[ -z ${WORKFLOW_CLI_TEMP-} ]]; then
+    WORKFLOW_CLI_TEMP=$(mktemp -d 2>/dev/null || mktemp -d -t kubernetes.XXXXXX)
+    trap cleanup-temp-dir EXIT
+  fi
+}
+
+
+WORFLOW_CLI_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
+
+TRANSLATIONS="pkg/i18n/translations"
+WORFLOW_CLI_FILES=()
+WORFLOW_CLI_DEFAULT_LOCATIONS=(
+  "cmd"
+  "internal/parser"
+)
+
+generate_pot="false"
+generate_mo="false"
+fix_translations="false"
+
+while getopts "hf:xkg" opt; do
+  case ${opt} in
+    h)
+      echo "$0 [-f files] [-x] [-k] [-g]"
+      echo " -f <file-path>: Files to process"
+      echo " -x extract strings to a POT file"
+      echo " -k fix .po files; deprecate translations by marking them obsolete and supply default messages"
+      echo " -g sort .po files and generate .mo files"
+      exit 0
+      ;;
+    f)
+      WORFLOW_CLI_FILES+=("${OPTARG}")
+      ;;
+    x)
+      generate_pot="true"
+      ;;
+    k)
+      fix_translations="true"
+      ;;
+    g)
+      generate_mo="true"
+      ;;
+    \?)
+      echo "[-f <files>] -x -g" >&2
+      exit 1
+      ;;
+  esac
+done
+
+if [[ ${#WORFLOW_CLI_FILES} -eq 0 ]]; then
+  WORFLOW_CLI_FILES+=("${WORFLOW_CLI_DEFAULT_LOCATIONS[@]}")
+fi
+
+if ! which go-xgettext > /dev/null; then
+  echo 'Can not find go-xgettext, install with:'
+  echo 'go install github.com/gosexy/gettext/go-xgettext@latest'
+  exit 1
+fi
+
+if ! which msgfmt > /dev/null; then
+  echo 'Can not find msgfmt, install with:'
+  echo 'apt-get install gettext'
+  exit 1
+fi
+
+if [[ "${generate_pot}" == "true" ]]; then
+  echo "Extracting strings to POT"
+  # shellcheck disable=SC2046
+  go-xgettext -k=i18n.T $(grep -lr "i18n.T" "${WORFLOW_CLI_FILES[@]}") > tmp.pot
+  perl -pi -e 's/CHARSET/UTF-8/' tmp.pot
+  perl -pi -e 's/\\(?!n"\n|")/\\\\/g' tmp.pot
+  ensure-temp-dir
+  if msgcat -s tmp.pot > "${WORKFLOW_CLI_TEMP}/template.pot"; then
+    mv "${WORKFLOW_CLI_TEMP}/template.pot" "${TRANSLATIONS}/drycc/template.pot"
+    rm tmp.pot
+  else
+    echo "Failed to update template.pot"
+    exit 1
+  fi
+fi
+
+if [[ "${fix_translations}" == "true" ]]; then
+  echo "Fixing .po files"
+  ensure-temp-dir
+  for PO_FILE in "${TRANSLATIONS}"/drycc/*/LC_MESSAGES/cli.po; do
+    TMP="${WORKFLOW_CLI_TEMP}/fix.po"
+    msgen "${TRANSLATIONS}/drycc/template.pot" | \
+      msgmerge --no-fuzzy-matching "${PO_FILE}" - > "${TMP}"
+    mv "${TMP}" "${PO_FILE}"
+  done
+fi
+
+if [[ "${generate_mo}" == "true" ]]; then
+  echo "Generating .po and .mo files"
+  for x in "${TRANSLATIONS}"/*/*/*/*.po; do
+    msgcat -s "${x}" > tmp.po
+    mv tmp.po "${x}"
+    echo "generating .mo file for: ${x}"
+    msgfmt "${x}" -o "$(dirname "${x}")/$(basename "${x}" .po).mo"
+  done
+fi
