diff --git a/cmd/tk/create_source_helm.go b/cmd/tk/create_source_helm.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b1f0e925e9a7512b89ae3de73f57f8624f3bb92
--- /dev/null
+++ b/cmd/tk/create_source_helm.go
@@ -0,0 +1,229 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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 (
+	"context"
+	"fmt"
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/spf13/cobra"
+	"io/ioutil"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"net/url"
+	"os"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/yaml"
+)
+
+var createSourceHelmCmd = &cobra.Command{
+	Use:   "helm [name]",
+	Short: "Create or update a HelmRepository source",
+	Long: `
+The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
+For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
+	Example: `  # Create a source from a public Helm repository
+  tk create source helm podinfo \
+    --url=https://stefanprodan.github.io/podinfo \
+    --interval=10m
+
+  # Create a source from a Helm repository using basic authentication
+  tk create source helm podinfo \
+    --url=https://stefanprodan.github.io/podinfo \
+    --username=username \
+    --password=password
+`,
+	RunE: createSourceHelmCmdRun,
+}
+
+var (
+	sourceHelmURL      string
+	sourceHelmUsername string
+	sourceHelmPassword string
+)
+
+func init() {
+	createSourceHelmCmd.Flags().StringVar(&sourceHelmURL, "url", "", "Helm repository address")
+	createSourceHelmCmd.Flags().StringVarP(&sourceHelmUsername, "username", "u", "", "basic authentication username")
+	createSourceHelmCmd.Flags().StringVarP(&sourceHelmPassword, "password", "p", "", "basic authentication password")
+
+	createSourceCmd.AddCommand(createSourceHelmCmd)
+}
+
+func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
+	if len(args) < 1 {
+		return fmt.Errorf("source name is required")
+	}
+	name := args[0]
+	secretName := fmt.Sprintf("helm-%s", name)
+
+	if sourceHelmURL == "" {
+		return fmt.Errorf("url is required")
+	}
+
+	tmpDir, err := ioutil.TempDir("", name)
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpDir)
+
+	if _, err := url.Parse(sourceHelmURL); err != nil {
+		return fmt.Errorf("url parse failed: %w", err)
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	helmRepository := sourcev1.HelmRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: sourcev1.HelmRepositorySpec{
+			URL: sourceHelmURL,
+			Interval: metav1.Duration{
+				Duration: interval,
+			},
+		},
+	}
+
+	if export {
+		return exportHelmRepository(helmRepository)
+	}
+
+	withAuth := false
+	if sourceHelmUsername != "" && sourceHelmPassword != "" {
+		logger.Actionf("applying secret with basic auth credentials")
+		secret := corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      secretName,
+				Namespace: namespace,
+			},
+			StringData: map[string]string{
+				"username": sourceHelmUsername,
+				"password": sourceHelmPassword,
+			},
+		}
+		if err := upsertSecret(ctx, kubeClient, secret); err != nil {
+			return err
+		}
+		withAuth = true
+	}
+
+	if withAuth {
+		logger.Successf("authentication configured")
+	}
+
+	logger.Generatef("generating source")
+
+	if withAuth {
+		helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{
+			Name: secretName,
+		}
+	}
+
+	logger.Actionf("applying source")
+	if err := upsertHelmRepository(ctx, kubeClient, helmRepository); err != nil {
+		return err
+	}
+
+	logger.Waitingf("waiting for index download")
+	if err := wait.PollImmediate(pollInterval, timeout,
+		isHelmRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
+		return err
+	}
+
+	logger.Successf("index download completed")
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}
+	err = kubeClient.Get(ctx, namespacedName, &helmRepository)
+	if err != nil {
+		return fmt.Errorf("helm index failed: %w", err)
+	}
+
+	if helmRepository.Status.Artifact != nil {
+		logger.Successf("fetched revision: %s", helmRepository.Status.Artifact.Revision)
+	} else {
+		return fmt.Errorf("index download failed, artifact not found")
+	}
+
+	return nil
+}
+
+func upsertHelmRepository(ctx context.Context, kubeClient client.Client, helmRepository sourcev1.HelmRepository) error {
+	namespacedName := types.NamespacedName{
+		Namespace: helmRepository.GetNamespace(),
+		Name:      helmRepository.GetName(),
+	}
+
+	var existing sourcev1.HelmRepository
+	err := kubeClient.Get(ctx, namespacedName, &existing)
+	if err != nil {
+		if errors.IsNotFound(err) {
+			if err := kubeClient.Create(ctx, &helmRepository); err != nil {
+				return err
+			} else {
+				logger.Successf("source created")
+				return nil
+			}
+		}
+		return err
+	}
+
+	existing.Spec = helmRepository.Spec
+	if err := kubeClient.Update(ctx, &existing); err != nil {
+		return err
+	}
+
+	logger.Successf("source updated")
+	return nil
+}
+
+func exportHelmRepository(source sourcev1.HelmRepository) error {
+	gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)
+	export := sourcev1.HelmRepository{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       gvk.Kind,
+			APIVersion: gvk.GroupVersion().String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      source.Name,
+			Namespace: source.Namespace,
+		},
+		Spec: source.Spec,
+	}
+
+	data, err := yaml.Marshal(export)
+	if err != nil {
+		return err
+	}
+
+	fmt.Println("---")
+	fmt.Println(string(data))
+	return nil
+}
diff --git a/cmd/tk/delete_source_helm.go b/cmd/tk/delete_source_helm.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dbe8397093c7abb38b07bf35f087f48651447dd
--- /dev/null
+++ b/cmd/tk/delete_source_helm.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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 (
+	"context"
+	"fmt"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/manifoldco/promptui"
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var deleteSourceHelmCmd = &cobra.Command{
+	Use:   "helm [name]",
+	Short: "Delete a HelmRepository source",
+	Long:  "The delete source helm command deletes the given HelmRepository from the cluster.",
+	Example: `  # Delete a Helm repository
+  tk delete source helm podinfo
+`,
+	RunE: deleteSourceHelmCmdRun,
+}
+
+func init() {
+	deleteSourceCmd.AddCommand(deleteSourceHelmCmd)
+}
+
+func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
+	if len(args) < 1 {
+		return fmt.Errorf("name is required")
+	}
+	name := args[0]
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}
+
+	var helmRepository sourcev1.HelmRepository
+	err = kubeClient.Get(ctx, namespacedName, &helmRepository)
+	if err != nil {
+		return err
+	}
+
+	if !deleteSilent {
+		prompt := promptui.Prompt{
+			Label:     "Are you sure you want to delete this source",
+			IsConfirm: true,
+		}
+		if _, err := prompt.Run(); err != nil {
+			return fmt.Errorf("aborting")
+		}
+	}
+
+	logger.Actionf("deleting source %s in %s namespace", name, namespace)
+	err = kubeClient.Delete(ctx, &helmRepository)
+	if err != nil {
+		return err
+	}
+	logger.Successf("source deleted")
+
+	return nil
+}
diff --git a/cmd/tk/export_source_helm.go b/cmd/tk/export_source_helm.go
new file mode 100644
index 0000000000000000000000000000000000000000..1026693c744df5cd493a74fb08a9b7189799582e
--- /dev/null
+++ b/cmd/tk/export_source_helm.go
@@ -0,0 +1,139 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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 (
+	"context"
+	"fmt"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/yaml"
+)
+
+var exportSourceHelmCmd = &cobra.Command{
+	Use:   "helm [name]",
+	Short: "Export HelmRepository sources in YAML format",
+	Long:  "The export source git command exports on or all HelmRepository sources in YAML format.",
+	Example: `  # Export all HelmRepository sources
+  tk export source helm --all > sources.yaml
+
+  # Export a HelmRepository source including the basic auth credentials
+  tk export source helm my-private-repo --with-credentials > source.yaml
+`,
+	RunE: exportSourceHelmCmdRun,
+}
+
+func init() {
+	exportSourceCmd.AddCommand(exportSourceHelmCmd)
+}
+
+func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
+	if !exportAll && len(args) < 1 {
+		return fmt.Errorf("name is required")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	if exportAll {
+		var list sourcev1.HelmRepositoryList
+		err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
+		if err != nil {
+			return err
+		}
+
+		if len(list.Items) == 0 {
+			logger.Failuref("no source found in %s namespace", namespace)
+			return nil
+		}
+
+		for _, repository := range list.Items {
+			if err := exportHelmRepository(repository); err != nil {
+				return err
+			}
+			if exportSourceWithCred {
+				if err := exportHelmCredentials(ctx, kubeClient, repository); err != nil {
+					return err
+				}
+			}
+		}
+	} else {
+		name := args[0]
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+			Name:      name,
+		}
+		var repository sourcev1.HelmRepository
+		err = kubeClient.Get(ctx, namespacedName, &repository)
+		if err != nil {
+			return err
+		}
+		if err := exportHelmRepository(repository); err != nil {
+			return err
+		}
+		if exportSourceWithCred {
+			return exportHelmCredentials(ctx, kubeClient, repository)
+		}
+	}
+	return nil
+}
+
+func exportHelmCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.HelmRepository) error {
+	if source.Spec.SecretRef != nil {
+		namespacedName := types.NamespacedName{
+			Namespace: source.Namespace,
+			Name:      source.Spec.SecretRef.Name,
+		}
+		var cred corev1.Secret
+		err := kubeClient.Get(ctx, namespacedName, &cred)
+		if err != nil {
+			return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
+		}
+
+		exported := corev1.Secret{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Secret",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      namespacedName.Name,
+				Namespace: namespacedName.Namespace,
+			},
+			Data: cred.Data,
+			Type: cred.Type,
+		}
+
+		data, err := yaml.Marshal(exported)
+		if err != nil {
+			return err
+		}
+
+		fmt.Println("---")
+		fmt.Println(string(data))
+	}
+	return nil
+}
diff --git a/cmd/tk/get_source_helm.go b/cmd/tk/get_source_helm.go
new file mode 100644
index 0000000000000000000000000000000000000000..04d46635733f2c72ac8bdf8c8b0f0ea758bfc73b
--- /dev/null
+++ b/cmd/tk/get_source_helm.go
@@ -0,0 +1,80 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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 (
+	"context"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+var getSourceHelmCmd = &cobra.Command{
+	Use:   "helm",
+	Short: "Get HelmRepository source statuses",
+	Long:  "The get sources helm command prints the status of the HelmRepository sources.",
+	Example: `  # List all Helm repositories and their status
+  tk get sources helm
+`,
+	RunE: getSourceHelmCmdRun,
+}
+
+func init() {
+	getSourceCmd.AddCommand(getSourceHelmCmd)
+}
+
+func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	var list sourcev1.HelmRepositoryList
+	err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
+	if err != nil {
+		return err
+	}
+
+	if len(list.Items) == 0 {
+		logger.Failuref("no sources found in %s namespace", namespace)
+		return nil
+	}
+
+	for _, source := range list.Items {
+		isInitialized := false
+		for _, condition := range source.Status.Conditions {
+			if condition.Type == sourcev1.ReadyCondition {
+				if condition.Status != corev1.ConditionFalse {
+					logger.Successf("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision)
+				} else {
+					logger.Failuref("%s %s", source.GetName(), condition.Message)
+				}
+				isInitialized = true
+				break
+			}
+		}
+		if !isInitialized {
+			logger.Failuref("%s is not ready", source.GetName())
+		}
+	}
+	return nil
+}