diff --git a/README.md b/README.md
index 337a59a9edfe1d7103be93cf1be8630774665ef4..40aea5545fe1b5fb857cb5fd20ed4ef285e3de41 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ including installing pre-releases.
   - [Kubernetes Distribution Notes](docs/kubernetes_distros.md)
   - [Frequently Asked Questions](docs/install_faq.md)
 - [Using Helm](docs/using_helm.md)
+  - [Plugins](docs/plugins.md)
 - [Developing Charts](docs/charts.md)
 	- [Chart Lifecycle Hooks](docs/charts_hooks.md)
 	- [Chart Tips and Tricks](docs/charts_tips_and_tricks.md)
diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go
index d9b169f0008de75408305a44c6a783ace9ecc103..59f88f6fb9afe3f8118d26dc5b3bf247c74ade9f 100644
--- a/cmd/helm/helm.go
+++ b/cmd/helm/helm.go
@@ -31,6 +31,7 @@ import (
 	"k8s.io/kubernetes/pkg/client/restclient"
 	"k8s.io/kubernetes/pkg/client/unversioned"
 
+	"k8s.io/helm/cmd/helm/helmpath"
 	"k8s.io/helm/pkg/kube"
 )
 
@@ -91,7 +92,7 @@ func newRootCmd(out io.Writer) *cobra.Command {
 	p.StringVar(&helmHome, "home", home, "location of your Helm config. Overrides $HELM_HOME")
 	p.StringVar(&tillerHost, "host", thost, "address of tiller. Overrides $HELM_HOST")
 	p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use")
-	p.BoolVarP(&flagDebug, "debug", "", false, "enable verbose output")
+	p.BoolVar(&flagDebug, "debug", false, "enable verbose output")
 
 	// Tell gRPC not to log to console.
 	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
@@ -124,6 +125,10 @@ func newRootCmd(out io.Writer) *cobra.Command {
 		// Deprecated
 		rup,
 	)
+
+	// Find and add plugins
+	loadPlugins(cmd, helmpath.Home(homePath()), out)
+
 	return cmd
 }
 
@@ -141,7 +146,7 @@ func setupConnection(c *cobra.Command, args []string) error {
 			return err
 		}
 
-		tillerHost = fmt.Sprintf(":%d", tunnel.Local)
+		tillerHost = fmt.Sprintf("localhost:%d", tunnel.Local)
 		if flagDebug {
 			fmt.Printf("Created tunnel using local port: '%d'\n", tunnel.Local)
 		}
@@ -151,6 +156,7 @@ func setupConnection(c *cobra.Command, args []string) error {
 	if flagDebug {
 		fmt.Printf("SERVER: %q\n", tillerHost)
 	}
+	// Plugin support.
 	return nil
 }
 
diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go
index ac03e178a666f299e5188888b868f119f4cac8f0..682709f2d4cefcd53abdfaec026dc6f0490ace22 100644
--- a/cmd/helm/helm_test.go
+++ b/cmd/helm/helm_test.go
@@ -241,7 +241,7 @@ func tempHelmHome(t *testing.T) (string, error) {
 //
 // t is used only for logging.
 func ensureTestHome(home helmpath.Home, t *testing.T) error {
-	configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Starters()}
+	configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Plugins(), home.Starters()}
 	for _, p := range configDirectories {
 		if fi, err := os.Stat(p); err != nil {
 			if err := os.MkdirAll(p, 0755); err != nil {
diff --git a/cmd/helm/helmpath/helmhome.go b/cmd/helm/helmpath/helmhome.go
index 9289c9d451faa6b64a5b34c6dfd774a3c6b4f973..03f65c6bb02fa82866b1be66ff13f5da050a63e8 100644
--- a/cmd/helm/helmpath/helmhome.go
+++ b/cmd/helm/helmpath/helmhome.go
@@ -67,3 +67,8 @@ func (h Home) LocalRepository(paths ...string) string {
 	frag := append([]string{string(h), "repository/local"}, paths...)
 	return filepath.Join(frag...)
 }
+
+// Plugins returns the path to the plugins directory.
+func (h Home) Plugins() string {
+	return filepath.Join(string(h), "plugins")
+}
diff --git a/cmd/helm/init.go b/cmd/helm/init.go
index 8bce43384a2cc8fac6eb9095aa8b21be09b10317..d3e60f629a97666d2ae41a3a5e0dcf8e4d984d60 100644
--- a/cmd/helm/init.go
+++ b/cmd/helm/init.go
@@ -143,7 +143,7 @@ func (i *initCmd) run() error {
 //
 // If $HELM_HOME does not exist, this function will create it.
 func ensureHome(home helmpath.Home, out io.Writer) error {
-	configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Starters()}
+	configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Plugins(), home.Starters()}
 	for _, p := range configDirectories {
 		if fi, err := os.Stat(p); err != nil {
 			fmt.Fprintf(out, "Creating %s \n", p)
diff --git a/cmd/helm/plugins.go b/cmd/helm/plugins.go
new file mode 100644
index 0000000000000000000000000000000000000000..a9a4383f5e4a53d00ca8f85e423a0a9125cba933
--- /dev/null
+++ b/cmd/helm/plugins.go
@@ -0,0 +1,180 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/spf13/cobra"
+
+	"k8s.io/helm/cmd/helm/helmpath"
+	"k8s.io/helm/pkg/plugin"
+)
+
+const pluginEnvVar = "HELM_PLUGIN"
+
+// loadPlugins loads plugins into the command list.
+//
+// This follows a different pattern than the other commands because it has
+// to inspect its environment and then add commands to the base command
+// as it finds them.
+func loadPlugins(baseCmd *cobra.Command, home helmpath.Home, out io.Writer) {
+	plugdirs := os.Getenv(pluginEnvVar)
+	if plugdirs == "" {
+		plugdirs = home.Plugins()
+	}
+
+	found, err := findPlugins(plugdirs)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
+		return
+	}
+
+	// Now we create commands for all of these.
+	for _, plug := range found {
+		plug := plug
+		md := plug.Metadata
+		if md.Usage == "" {
+			md.Usage = fmt.Sprintf("the %q plugin", md.Name)
+		}
+
+		c := &cobra.Command{
+			Use:   md.Name,
+			Short: md.Usage,
+			Long:  md.Description,
+			RunE: func(cmd *cobra.Command, args []string) error {
+
+				k, u := manuallyProcessArgs(args)
+				if err := cmd.ParseFlags(k); err != nil {
+					return err
+				}
+
+				// Call setupEnv before PrepareCommand because
+				// PrepareCommand uses os.ExpandEnv and expects the
+				// setupEnv vars.
+				setupEnv(md.Name, plug.Dir, plugdirs, home)
+				main, argv := plug.PrepareCommand(u)
+
+				prog := exec.Command(main, argv...)
+				prog.Env = os.Environ()
+				prog.Stdout = out
+				prog.Stderr = os.Stderr
+				if err := prog.Run(); err != nil {
+					eerr := err.(*exec.ExitError)
+					os.Stderr.Write(eerr.Stderr)
+					return fmt.Errorf("plugin %q exited with error", md.Name)
+				}
+				return nil
+			},
+			// This passes all the flags to the subcommand.
+			DisableFlagParsing: true,
+		}
+
+		if md.UseTunnel {
+			c.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
+				// Parse the parent flag, but not the local flags.
+				k, _ := manuallyProcessArgs(args)
+				if err := c.Parent().ParseFlags(k); err != nil {
+					return err
+				}
+				return setupConnection(cmd, args)
+			}
+		}
+
+		// TODO: Make sure a command with this name does not already exist.
+		baseCmd.AddCommand(c)
+	}
+}
+
+// manuallyProcessArgs processes an arg array, removing special args.
+//
+// Returns two sets of args: known and unknown (in that order)
+func manuallyProcessArgs(args []string) ([]string, []string) {
+	known := []string{}
+	unknown := []string{}
+	kvargs := []string{"--host", "--kube-context", "--home"}
+	knownArg := func(a string) bool {
+		for _, pre := range kvargs {
+			if strings.HasPrefix(a, pre+"=") {
+				return true
+			}
+		}
+		return false
+	}
+	for i := 0; i < len(args); i++ {
+		switch a := args[i]; a {
+		case "--debug":
+			known = append(known, a)
+		case "--host", "--kube-context", "--home":
+			known = append(known, a, args[i+1])
+			i++
+		default:
+			if knownArg(a) {
+				known = append(known, a)
+				continue
+			}
+			unknown = append(unknown, a)
+		}
+	}
+	return known, unknown
+}
+
+// findPlugins returns a list of YAML files that describe plugins.
+func findPlugins(plugdirs string) ([]*plugin.Plugin, error) {
+	found := []*plugin.Plugin{}
+	// Let's get all UNIXy and allow path separators
+	for _, p := range filepath.SplitList(plugdirs) {
+		matches, err := plugin.LoadAll(p)
+		if err != nil {
+			return matches, err
+		}
+		found = append(found, matches...)
+	}
+	return found, nil
+}
+
+// setupEnv prepares os.Env for plugins. It operates on os.Env because
+// the plugin subsystem itself needs access to the environment variables
+// created here.
+func setupEnv(shortname, base, plugdirs string, home helmpath.Home) {
+	// Set extra env vars:
+	for key, val := range map[string]string{
+		"HELM_PLUGIN_NAME": shortname,
+		"HELM_PLUGIN_DIR":  base,
+		"HELM_BIN":         os.Args[0],
+
+		// Set vars that may not have been set, and save client the
+		// trouble of re-parsing.
+		pluginEnvVar: plugdirs,
+		homeEnvVar:   home.String(),
+
+		// Set vars that convey common information.
+		"HELM_PATH_REPOSITORY":       home.Repository(),
+		"HELM_PATH_REPOSITORY_FILE":  home.RepositoryFile(),
+		"HELM_PATH_CACHE":            home.Cache(),
+		"HELM_PATH_LOCAL_REPOSITORY": home.LocalRepository(),
+		//"HELM_PATH_STARTER":          home.Starter(),
+
+		"TILLER_HOST": tillerHost,
+	} {
+		os.Setenv(key, val)
+	}
+}
diff --git a/cmd/helm/plugins_test.go b/cmd/helm/plugins_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..77828ea3bab8db02586c541fa8b0e84445450002
--- /dev/null
+++ b/cmd/helm/plugins_test.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"bytes"
+	"os"
+	"strings"
+	"testing"
+
+	"k8s.io/helm/cmd/helm/helmpath"
+
+	"github.com/spf13/cobra"
+)
+
+func TestManuallyProcessArgs(t *testing.T) {
+	input := []string{
+		"--debug",
+		"--foo",
+		"bar",
+		"--host",
+		"example.com",
+		"--kube-context",
+		"test1",
+		"--home=/tmp",
+		"command",
+	}
+
+	expectKnown := []string{
+		"--debug", "--host", "example.com", "--kube-context", "test1", "--home=/tmp",
+	}
+
+	expectUnknown := []string{
+		"--foo", "bar", "command",
+	}
+
+	known, unknown := manuallyProcessArgs(input)
+
+	for i, k := range known {
+		if k != expectKnown[i] {
+			t.Errorf("expected known flag %d to be %q, got %q", i, expectKnown[i], k)
+		}
+	}
+	for i, k := range unknown {
+		if k != expectUnknown[i] {
+			t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k)
+		}
+	}
+
+}
+
+func TestLoadPlugins(t *testing.T) {
+	// Set helm home to point to testdata
+	old := helmHome
+	helmHome = "testdata/helmhome"
+	defer func() {
+		helmHome = old
+	}()
+	hh := helmpath.Home(homePath())
+
+	out := bytes.NewBuffer(nil)
+	cmd := &cobra.Command{}
+	loadPlugins(cmd, hh, out)
+
+	envs := strings.Join([]string{
+		"fullenv",
+		"fullenv.yaml",
+		hh.Plugins(),
+		hh.String(),
+		hh.Repository(),
+		hh.RepositoryFile(),
+		hh.Cache(),
+		hh.LocalRepository(),
+		os.Args[0],
+	}, "\n")
+
+	// Test that the YAML file was correctly converted to a command.
+	tests := []struct {
+		use    string
+		short  string
+		long   string
+		expect string
+		args   []string
+	}{
+		{"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}},
+		{"echo", "echo stuff", "This echos stuff", "hello\n", []string{}},
+		{"env", "env stuff", "show the env", hh.String() + "\n", []string{}},
+		{"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}},
+	}
+
+	plugins := cmd.Commands()
+	for i := 0; i < len(plugins); i++ {
+		out.Reset()
+		tt := tests[i]
+		pp := plugins[i]
+		if pp.Use != tt.use {
+			t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pp.Use)
+		}
+		if pp.Short != tt.short {
+			t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pp.Short)
+		}
+		if pp.Long != tt.long {
+			t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long)
+		}
+		if err := pp.RunE(pp, tt.args); err != nil {
+			t.Errorf("Error running %s: %s", tt.use, err)
+		}
+		if out.String() != tt.expect {
+			t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
+		}
+	}
+}
diff --git a/cmd/helm/testdata/helmhome/plugins/args.sh b/cmd/helm/testdata/helmhome/plugins/args.sh
new file mode 100755
index 0000000000000000000000000000000000000000..678b4eff551c32b69d6aa461dfa7e0923e07e69c
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/args.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo $*
diff --git a/cmd/helm/testdata/helmhome/plugins/args.yaml b/cmd/helm/testdata/helmhome/plugins/args.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dbea13673a91e9086910caaf6ab480fe5abbfe23
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/args.yaml
@@ -0,0 +1,4 @@
+name: args
+usage: "echo args"
+description: "This echos args"
+command: "$HELM_HOME/plugins/args.sh"
diff --git a/cmd/helm/testdata/helmhome/plugins/echo.yaml b/cmd/helm/testdata/helmhome/plugins/echo.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7b9362a08712590807ae46bb53dbb770270a6998
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/echo.yaml
@@ -0,0 +1,4 @@
+name: echo
+usage: "echo stuff"
+description: "This echos stuff"
+command: "echo hello"
diff --git a/cmd/helm/testdata/helmhome/plugins/env.yaml b/cmd/helm/testdata/helmhome/plugins/env.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c8ae403502e229ce5b487fca628fbb16418a8d1f
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/env.yaml
@@ -0,0 +1,4 @@
+name: env
+usage: "env stuff"
+description: "show the env"
+command: "echo $HELM_HOME"
diff --git a/cmd/helm/testdata/helmhome/plugins/fullenv.sh b/cmd/helm/testdata/helmhome/plugins/fullenv.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e720473331b06cb57704c4d62b8517f505ceab04
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/fullenv.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+echo $HELM_PLUGIN_SHORTNAME
+echo $HELM_PLUGIN_NAME
+echo $HELM_PLUGIN
+echo $HELM_HOME
+echo $HELM_PATH_REPOSITORY
+echo $HELM_PATH_REPOSITORY_FILE
+echo $HELM_PATH_CACHE
+echo $HELM_PATH_LOCAL_REPOSITORY
+echo $HELM_BIN
diff --git a/cmd/helm/testdata/helmhome/plugins/fullenv.yaml b/cmd/helm/testdata/helmhome/plugins/fullenv.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a6f4c5a4e051cb0398422104dddd54d5dcb5274c
--- /dev/null
+++ b/cmd/helm/testdata/helmhome/plugins/fullenv.yaml
@@ -0,0 +1,4 @@
+name: fullenv
+usage: "show env vars"
+description: "show all env vars"
+command: "$HELM_HOME/plugins/fullenv.sh"
diff --git a/docs/plugins.md b/docs/plugins.md
new file mode 100644
index 0000000000000000000000000000000000000000..d8aae6bea3b3ba4b568939b321c2cfbf9c22115b
--- /dev/null
+++ b/docs/plugins.md
@@ -0,0 +1,176 @@
+# The Helm Plugins Guide
+
+Helm 2.1.0 introduced the concept of a client-side Helm _plugin_. A plugin is a
+tool that can be accessed through the `helm` CLI, but which is not part of the
+built-in Helm codebase.
+
+This guide explains how to use and create plugins.
+
+## An Overview
+
+Helm plugins are add-on tools that integrate seemlessly with Helm. They provide
+a way to extend the core feature set of Helm, but without requiring every new
+feature to be written in Go and added to the core tool.
+
+Helm plugins have the following features:
+
+- They can be added and removed from a Helm installation without impacting the
+  core Helm tool.
+- They can be written in any programming language.
+- They integrate with Helm, and will show up in `helm help` and other places.
+
+Helm plugins live in `$(helm home)/plugins`.
+
+The Helm plugin model is partially modeled on Git's plugin model. To that end,
+you may sometimes hear `helm` referred to as the _porcelain_ layer, with
+plugins being the _plumbing_. This is a shorthand way of suggesting that
+Helm provides the user experience and top level processing logic, while the
+plugins do the "detail work" of performing a desired action.
+
+## Installing a Plugin
+
+A Helm plugin management system is in the works. But in the short term, plugins
+are installed by copying the plugin directory into `$(helm home)/plugins`.
+
+```console
+$ cp -a myplugin/ $(helm home)/plugins/
+```
+
+If you have a plugin tar distribution, simply untar the plugin into the
+`$(helm home)/plugins` directory.
+
+## Building Plugins
+
+In many ways, a plugin is similar to a chart. Each plugin has a top-level
+directory, and then a `plugin.yaml` file.
+
+```
+$(helm home)/plugins/
+  |- keybase/
+      |
+      |- plugin.yaml
+      |- keybase.sh
+
+```
+
+In the example above, the `keybase` plugin is contained inside of a directory
+named `keybase`. It has two files: `plugin.yaml` (required) and an executable
+script, `keybase.sh` (optional).
+
+The core of a plugin is a simple YAML file named `plugin.yaml`.
+Here is a plugin YAML for a plugin that adds support for Keybase operations:
+
+```
+name: "keybase"
+version: "0.1.0"
+usage: "Integreate Keybase.io tools with Helm"
+description: |-
+  This plugin provides Keybase services to Helm.
+ignoreFlags: false
+useTunnel: false
+command: "$HELM_PLUGIN_DIR/keybase.sh"
+```
+
+The `name` is the name of the plugin. When Helm executes it plugin, this is the
+name it will use (e.g. `helm NAME` will invoke this plugin).
+
+_`name` should match the directory name._ In our example above, that means the
+plugin with `name: keybase` should be contained in a directory named `keybase`.
+
+Restrictions on `name`:
+
+- `name` cannot duplicate one of the existing `helm` top-level commands.
+- `name` must be restricted to the characters ASCII a-z, A-Z, 0-9, `_` and `-`.
+
+`version` is the SemVer 2 version of the plugin.
+`usage` and `description` are both used to generate the help text of a command.
+
+The `ignoreFlags` switch tells Helm to _not_ pass flags to the plugin. So if a
+plugin is called with `helm myplugin --foo` and `ignoreFlags: true`, then `--foo`
+is silently discarded.
+
+The `useTunnel` switch indicates that the plugin needs a tunnel to Tiller. This
+should be set to `true` _anytime a plugin talks to Tiller_. It will cause Helm
+to open a tunnel, and then set `$TILLER_HOST` to the right local address for that
+tunnel. But don't worry: if Helm detects that a tunnel is not necessary because
+Tiller is running locally, it will not create the tunnel.
+
+Finally, and most importantly, `command` is the command that this plugin will
+execute when it is called. Environment variables are interpolated before the plugin
+is executed. The pattern above illustrates the preferred way to indicate where
+the plugin program lives.
+
+There are some strategies for working with plugin commands:
+
+- If a plugin includes an executable, the executable for a `command:` should be
+  packaged in the plugin directory.
+- The `command:` line will have any environment variables expanded before
+  execution. `$HELM_PLUGIN_DIR` will point to the plugin directory.
+- The command itself is not executed in a shell. So you can't oneline a shell script.
+- Helm injects lots of configuration into environment variables. Take a look at
+  the environment to see what information is available.
+- Helm makes no assumptions about the language of the plugin. You can write it
+  in whatever you prefer.
+- Commands are responsible for implementing specific help text for `-h` and `--help`.
+  Helm will use `usage` and `description` for `helm help` and `helm help myplugin`,
+  but will not handle `helm myplugin --help`.
+
+## Environment Variables
+
+When Helm executes a plugin, it passes the outer environment to the plugin, and
+also injects some additional environment variables.
+
+Variables like `KUBECONFIG` are set for the plugin if they are set in the
+outer environment.
+
+The following variables are guaranteed to be set:
+
+- `HELM_PLUGIN`: The path to the plugins directory
+- `HELM_PLUGIN_NAME`: The name of the plugin, as invoked by `helm`. So
+  `helm myplug` will have the short name `myplug`.
+- `HELM_PLUGIN_DIR`: The directory that contains the plugin.
+- `HELM_BIN`: The path to the `helm` command (as executed by the user).
+- `HELM_HOME`: The path to the Helm home.
+- `HELM_PATH_*`: Paths to important Helm files and directories are stored in
+  environment variables prefixed by `HELM_PATH`.
+- `TILLER_HOST`: The `domain:port` to Tiller. If a tunnel is created, this
+  will point to the local endpoint for the tunnel. Otherwise, it will point
+  to `$HELM_HOST`, `--host`, or the default host (according to Helm's rules of
+  precedence).
+
+While `HELM_HOST` _may_ be set, there is no guarantee that it will point to the
+correct Tiller instance. This is done to allow plugin developer to access
+`HELM_HOST` in its raw state when the plugin itself needs to manually configure
+a connection.
+
+## A Note on `useTunnel`
+
+If a plugin specifies `useTunnel: true`, Helm will do the following (in order):
+
+1. Parse global flags and the environment
+2. Create the tunnel
+3. Set `TILLER_HOST`
+4. Execute the plugin
+5. Close the tunnel
+
+The tunnel is removed as soon as the `command` returns. So, for example, a
+command cannot background a process and assume that that process will be able
+to use the tunnel.
+
+## A Note on Flag Parsing
+
+When executing a plugin, Helm will parse global flags for its own use, but pass
+all flags to the plugin.
+
+Plugins MUST NOT produce an error for the following flags:
+
+- `--debug`
+- `--home`
+- `--host`
+- `--kube-context`
+- `-h`
+- `--help`
+
+Plugins _should_ display help text and then exit for `-h` and `--help`. In all
+other cases, plugins may simply ignore the flags.
+
diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..bc8cbb73283f27757c9c5709c62e7950af17066f
--- /dev/null
+++ b/pkg/plugin/plugin.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package plugin
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/ghodss/yaml"
+)
+
+// PluginFileName is the name of a plugin file.
+const PluginFileName = "plugin.yaml"
+
+// Metadata describes a plugin.
+//
+// This is the plugin equivalent of a chart.Metadata.
+type Metadata struct {
+	// Name is the name of the plugin
+	Name string `json:"name"`
+
+	// Version is a SemVer 2 version of the plugin.
+	Version string `json:"version"`
+
+	// Usage is the single-line usage text shown in help
+	Usage string `json:"usage"`
+
+	// Description is a long description shown in places like `helm help`
+	Description string `json:"description"`
+
+	// Command is the command, as a single string.
+	//
+	// The command will be passed through environment expansion, so env vars can
+	// be present in this command. Unless IgnoreFlags is set, this will
+	// also merge the flags passed from Helm.
+	//
+	// Note that command is not executed in a shell. To do so, we suggest
+	// pointing the command to a shell script.
+	Command string `json:"command"`
+
+	// IgnoreFlags ignores any flags passed in from Helm
+	//
+	// For example, if the plugin is invoked as `helm --debug myplugin`, if this
+	// is false, `--debug` will be appended to `--command`. If this is true,
+	// the `--debug` flag will be discarded.
+	IgnoreFlags bool `json:"ignoreFlags"`
+
+	// UseTunnel indicates that this command needs a tunnel.
+	// Setting this will cause a number of side effects, such as the
+	// automatic setting of HELM_HOST.
+	UseTunnel bool `json:"useTunnel"`
+}
+
+// Plugin represents a plugin.
+type Plugin struct {
+	// Metadata is a parsed representation of a plugin.yaml
+	Metadata *Metadata
+	// Dir is the string path to the directory that holds the plugin.
+	Dir string
+}
+
+// PrepareCommand takes a Plugin.Command and prepares it for execution.
+//
+// It merges extraArgs into any arguments supplied in the plugin. It
+// returns the name of the command and an args array.
+//
+// The result is suitable to pass to exec.Command.
+func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string) {
+	parts := strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
+	main := parts[0]
+	baseArgs := []string{}
+	if len(parts) > 1 {
+		baseArgs = parts[1:]
+	}
+	if !p.Metadata.IgnoreFlags {
+		baseArgs = append(baseArgs, extraArgs...)
+	}
+	return main, baseArgs
+}
+
+// LoadDir loads a plugin from the given directory.
+func LoadDir(dirname string) (*Plugin, error) {
+	data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName))
+	if err != nil {
+		return nil, err
+	}
+
+	plug := &Plugin{Dir: dirname}
+	if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
+		return nil, err
+	}
+	return plug, nil
+}
+
+// LoadAll loads all plugins found beneath the base directory.
+//
+// This scans only one directory level.
+func LoadAll(basedir string) ([]*Plugin, error) {
+	plugins := []*Plugin{}
+	// We want basedir/*/plugin.yaml
+	scanpath := filepath.Join(basedir, "*", PluginFileName)
+	matches, err := filepath.Glob(scanpath)
+	if err != nil {
+		return plugins, err
+	}
+
+	if matches == nil {
+		return plugins, nil
+	}
+
+	for _, yaml := range matches {
+		dir := filepath.Dir(yaml)
+		p, err := LoadDir(dir)
+		if err != nil {
+			return plugins, err
+		}
+		plugins = append(plugins, p)
+	}
+	return plugins, nil
+}
diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aa976546aca510dad4340cca4e1201a0e4e07397
--- /dev/null
+++ b/pkg/plugin/plugin_test.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package plugin
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestPrepareCommand(t *testing.T) {
+	p := &Plugin{
+		Dir: "/tmp", // Unused
+		Metadata: &Metadata{
+			Name:    "test",
+			Command: "echo -n foo",
+		},
+	}
+	argv := []string{"--debug", "--foo", "bar"}
+
+	cmd, args := p.PrepareCommand(argv)
+	if cmd != "echo" {
+		t.Errorf("Expected echo, got %q", cmd)
+	}
+
+	if l := len(args); l != 5 {
+		t.Errorf("expected 5 args, got %d", l)
+	}
+
+	expect := []string{"-n", "foo", "--debug", "--foo", "bar"}
+	for i := 0; i < len(args); i++ {
+		if expect[i] != args[i] {
+			t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
+		}
+	}
+
+	// Test with IgnoreFlags. This should omit --debug, --foo, bar
+	p.Metadata.IgnoreFlags = true
+	cmd, args = p.PrepareCommand(argv)
+	if cmd != "echo" {
+		t.Errorf("Expected echo, got %q", cmd)
+	}
+	if l := len(args); l != 2 {
+		t.Errorf("expected 2 args, got %d", l)
+	}
+	expect = []string{"-n", "foo"}
+	for i := 0; i < len(args); i++ {
+		if expect[i] != args[i] {
+			t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
+		}
+	}
+}
+
+func TestLoadDir(t *testing.T) {
+	dirname := "testdata/plugdir/hello"
+	plug, err := LoadDir(dirname)
+	if err != nil {
+		t.Fatalf("error loading Hello plugin: %s", err)
+	}
+
+	if plug.Dir != dirname {
+		t.Errorf("Expected dir %q, got %q", dirname, plug.Dir)
+	}
+
+	expect := Metadata{
+		Name:        "hello",
+		Version:     "0.1.0",
+		Usage:       "usage",
+		Description: "description",
+		Command:     "$HELM_PLUGIN_SELF/hello.sh",
+		UseTunnel:   true,
+		IgnoreFlags: true,
+	}
+
+	if reflect.DeepEqual(expect, plug.Metadata) {
+		t.Errorf("Expected name %v, got %v", expect, plug.Metadata)
+	}
+}
+
+func TestLoadAll(t *testing.T) {
+
+	// Verify that empty dir loads:
+	if plugs, err := LoadAll("testdata"); err != nil {
+		t.Fatalf("error loading dir with no plugins: %s", err)
+	} else if len(plugs) > 0 {
+		t.Fatalf("expected empty dir to have 0 plugins")
+	}
+
+	basedir := "testdata/plugdir"
+	plugs, err := LoadAll(basedir)
+	if err != nil {
+		t.Fatalf("Could not load %q: %s", basedir, err)
+	}
+
+	if l := len(plugs); l != 2 {
+		t.Fatalf("expected 2 plugins, found %d", l)
+	}
+
+	if plugs[0].Metadata.Name != "echo" {
+		t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name)
+	}
+	if plugs[1].Metadata.Name != "hello" {
+		t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name)
+	}
+}
diff --git a/pkg/plugin/testdata/plugdir/echo/plugin.yaml b/pkg/plugin/testdata/plugdir/echo/plugin.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..da6f656ebbc3d732fa1b4963b68de7a1f28722e8
--- /dev/null
+++ b/pkg/plugin/testdata/plugdir/echo/plugin.yaml
@@ -0,0 +1,6 @@
+name: "echo"
+version: "1.2.3"
+usage: "echo something"
+description: |-
+  This is a testing fixture.
+command: "echo Hello"
diff --git a/pkg/plugin/testdata/plugdir/hello/hello.sh b/pkg/plugin/testdata/plugdir/hello/hello.sh
new file mode 100755
index 0000000000000000000000000000000000000000..db7c0f54dafb9b1267e725b9ddad04c5d5c0f4c3
--- /dev/null
+++ b/pkg/plugin/testdata/plugdir/hello/hello.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+echo "Hello from a Helm plugin"
+
+echo "PARAMS"
+echo $*
+
+echo "ENVIRONMENT"
+echo $TILLER_HOST
+echo $HELM_HOME
+
+$HELM_BIN --host $TILLER_HOST ls --all
+
diff --git a/pkg/plugin/testdata/plugdir/hello/plugin.yaml b/pkg/plugin/testdata/plugdir/hello/plugin.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6415e6fa01a6085ee3639244a2c0f01c380d651c
--- /dev/null
+++ b/pkg/plugin/testdata/plugdir/hello/plugin.yaml
@@ -0,0 +1,8 @@
+name: "hello"
+version: "0.1.0"
+usage: "usage"
+description: |-
+  description
+command: "$HELM_PLUGIN_SELF/helm-hello"
+useTunnel: true
+ignoreFlags: true