diff --git a/cmd/dep.go b/cmd/pkg.go
similarity index 63%
rename from cmd/dep.go
rename to cmd/pkg.go
index dca4a950c3f33a9fa502561911cd9f6ad3bbb9cf..bf61331a875da7eb0c058c666dd1148dbbfb4f8d 100644
--- a/cmd/dep.go
+++ b/cmd/pkg.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 
 	"github.com/ksonnet/ksonnet/metadata"
+	"github.com/ksonnet/ksonnet/metadata/parts"
 	"github.com/ksonnet/ksonnet/utils"
 	"github.com/spf13/cobra"
 )
@@ -29,27 +30,30 @@ const (
 	flagName = "name"
 )
 
+var errInvalidSpec = fmt.Errorf("Command 'pkg install' requires a single argument of the form <registry>/<library>@<version>")
+
 func init() {
-	RootCmd.AddCommand(depCmd)
-	depCmd.AddCommand(depAddCmd)
-	depCmd.AddCommand(depListCmd)
-	depAddCmd.PersistentFlags().String(flagName, "", "Name to give the dependency")
+	RootCmd.AddCommand(pkgCmd)
+	pkgCmd.AddCommand(pkgInstallCmd)
+	pkgCmd.AddCommand(pkgListCmd)
+	pkgCmd.AddCommand(pkgDescribeCmd)
+	pkgInstallCmd.PersistentFlags().String(flagName, "", "Name to give the dependency")
 }
 
-var depCmd = &cobra.Command{
-	Use:   "dep",
-	Short: `Manage dependencies for the current ksonnet project`,
+var pkgCmd = &cobra.Command{
+	Use:   "pkg",
+	Short: `Manage packages and dependencies for the current ksonnet project`,
 	RunE: func(cmd *cobra.Command, args []string) error {
-		return fmt.Errorf("Command 'dep' requires a subcommand\n\n%s", cmd.UsageString())
+		return fmt.Errorf("Command 'pkg' requires a subcommand\n\n%s", cmd.UsageString())
 	},
 }
 
-var depAddCmd = &cobra.Command{
-	Use:   "add <registry>/<library>@<version>",
-	Short: `Add a dependency to current ksonnet application`,
+var pkgInstallCmd = &cobra.Command{
+	Use:   "install <registry>/<library>@<version>",
+	Short: `Install a package as a dependency in the current ksonnet application`,
 	RunE: func(cmd *cobra.Command, args []string) error {
 		if len(args) != 1 {
-			return fmt.Errorf("Command 'dep add' requires a single argument of the form <registry>/<library>@<version>")
+			return fmt.Errorf("Command 'pkg install' requires a single argument of the form <registry>/<library>@<version>")
 		}
 
 		registry, libID, name, version, err := parseDepSpec(cmd, args[0])
@@ -82,7 +86,7 @@ app repository.
 
 For example, inside a ksonnet application directory, run:
 
-  ks dep get incubator/nginx@v0.1
+  ks pkg install incubator/nginx@v0.1
 
 This can then be referenced in a source file in the ksonnet project:
 
@@ -95,14 +99,73 @@ be added with the 'ks registry' command.
 Note that multiple versions of the same ksonnet library can be cached and used
 in the same project, by explicitly passing in the '--name' flag. For example:
 
-  ks dep get incubator/nginx@v0.1 --name nginxv1
-  ks dep get incubator/nginx@v0.2 --name nginxv2
+  ks pkg install incubator/nginx@v0.1 --name nginxv1
+  ks pkg install incubator/nginx@v0.2 --name nginxv2
 
 With these commands, a user can 'import "kspkg://nginx1"', and
 'import "kspkg://nginx2"' with no conflict.`,
 }
 
-var depListCmd = &cobra.Command{
+var pkgDescribeCmd = &cobra.Command{
+	Use:   "describe [<registry-name>/]<package-name>",
+	Short: `Describe a ksonnet package`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		if len(args) != 1 {
+			return fmt.Errorf("Command 'pkg describe' requires a package name\n\n%s", cmd.UsageString())
+		}
+
+		registryName, libID, err := parsePkgSpec(args[0])
+		if err == errInvalidSpec {
+			registryName = ""
+			libID = args[0]
+		} else if err != nil {
+			return err
+		}
+
+		cwd, err := os.Getwd()
+		if err != nil {
+			return err
+		}
+		wd := metadata.AbsPath(cwd)
+
+		manager, err := metadata.Find(wd)
+		if err != nil {
+			return err
+		}
+
+		var lib *parts.Spec
+		if registryName == "" {
+			lib, err = manager.GetDependency(libID)
+			if err != nil {
+				return err
+			}
+		} else {
+			lib, err = manager.GetPackage(registryName, libID)
+			if err != nil {
+				return err
+			}
+		}
+
+		fmt.Println(`LIBRARY NAME:`)
+		fmt.Println(lib.Name)
+		fmt.Println()
+		fmt.Println(`DESCRIPTION:`)
+		fmt.Println(lib.Description)
+		fmt.Println()
+		fmt.Println(`PROTOTYPES:`)
+		for _, proto := range lib.Prototypes {
+			fmt.Printf("  %s\n", proto)
+		}
+		fmt.Println()
+
+		return nil
+	},
+
+	Long: `Output documentation for some ksonnet registry prototype uniquely identified in
+	the current ksonnet project by some 'registry-name'.`,
+}
+
+var pkgListCmd = &cobra.Command{
 	Use:   "list",
 	Short: `Lists information about all dependencies known to the current ksonnet app`,
 	RunE: func(cmd *cobra.Command, args []string) error {
@@ -114,7 +177,7 @@ var depListCmd = &cobra.Command{
 		)
 
 		if len(args) != 0 {
-			return fmt.Errorf("Command 'dep list' does not take arguments")
+			return fmt.Errorf("Command 'pkg list' does not take arguments")
 		}
 
 		cwd, err := os.Getwd()
@@ -165,16 +228,24 @@ var depListCmd = &cobra.Command{
 	},
 }
 
-func parseDepSpec(cmd *cobra.Command, spec string) (registry, libID, name, version string, err error) {
+func parsePkgSpec(spec string) (registry, libID string, err error) {
 	split := strings.SplitN(spec, "/", 2)
 	if len(split) < 2 {
-		return "", "", "", "", fmt.Errorf("Command 'dep add' requires a single argument of the form <registry>/<library>@<version>")
+		return "", "", errInvalidSpec
 	}
 	registry = split[0]
 	// Strip off the trailing `@version`.
 	libID = strings.SplitN(split[1], "@", 2)[0]
+	return
+}
+
+func parseDepSpec(cmd *cobra.Command, spec string) (registry, libID, name, version string, err error) {
+	registry, libID, err = parsePkgSpec(spec)
+	if err != nil {
+		return "", "", "", "", err
+	}
 
-	split = strings.Split(spec, "@")
+	split := strings.Split(spec, "@")
 	if len(split) > 2 {
 		return "", "", "", "", fmt.Errorf("Symbol '@' is only allowed once, at the end of the argument of the form <registry>/<library>@<version>")
 	}
diff --git a/cmd/registry.go b/cmd/registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..7149d18f8fa9b88307881f8211d6c6ea399de656
--- /dev/null
+++ b/cmd/registry.go
@@ -0,0 +1,146 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/ksonnet/ksonnet/metadata"
+	"github.com/ksonnet/ksonnet/utils"
+	"github.com/spf13/cobra"
+)
+
+func init() {
+	RootCmd.AddCommand(registryCmd)
+	registryCmd.AddCommand(registryListCmd)
+	registryCmd.AddCommand(registryDescribeCmd)
+}
+
+var registryCmd = &cobra.Command{
+	Use:   "registry",
+	Short: `Manage registries for current project`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return fmt.Errorf("Command 'registry' requires a subcommand\n\n%s", cmd.UsageString())
+	},
+	Long: `Manage and inspect ksonnet registries.
+
+Registries contain a set of versioned libraries that the user can install and
+manage in a ksonnet project using the CLI. A typical library contains:
+
+  1. A set of "parts", pre-fabricated API objects which can be combined together
+     to configure a Kubernetes application for some task. For example, the Redis
+     library may contain a Deployment, a Service, a Secret, and a
+     PersistentVolumeClaim, but if the user is operating it as a cache, they may
+     only need the first three of these.
+  2. A set of "prototypes", which are pre-fabricated combinations of these
+     parts, made to make it easier to get started using a library. See the
+     documentation for 'ks prototype' for more information.`,
+}
+
+var registryListCmd = &cobra.Command{
+	Use:   "list",
+	Short: `List all registries known to current ksonnet app`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		const (
+			nameHeader     = "NAME"
+			protocolHeader = "PROTOCOL"
+			uriHeader      = "URI"
+		)
+
+		if len(args) != 0 {
+			return fmt.Errorf("Command 'registry list' does not take arguments")
+		}
+
+		cwd, err := os.Getwd()
+		if err != nil {
+			return err
+		}
+		wd := metadata.AbsPath(cwd)
+
+		manager, err := metadata.Find(wd)
+		if err != nil {
+			return err
+		}
+
+		app, err := manager.AppSpec()
+		if err != nil {
+			return err
+		}
+
+		rows := [][]string{
+			[]string{nameHeader, protocolHeader, uriHeader},
+			[]string{
+				strings.Repeat("=", len(nameHeader)),
+				strings.Repeat("=", len(protocolHeader)),
+				strings.Repeat("=", len(uriHeader)),
+			},
+		}
+		for name, regRef := range app.Registries {
+			rows = append(rows, []string{name, regRef.Protocol, regRef.URI})
+		}
+
+		formatted, err := utils.PadRows(rows)
+		if err != nil {
+			return err
+		}
+		fmt.Print(formatted)
+		return nil
+	},
+}
+
+var registryDescribeCmd = &cobra.Command{
+	Use:   "describe <registry-name>",
+	Short: `Describe a ksonnet registry`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		if len(args) != 1 {
+			return fmt.Errorf("Command 'registry describe' takes one argument, which is the name of the registry to describe")
+		}
+		name := args[0]
+
+		cwd, err := os.Getwd()
+		if err != nil {
+			return err
+		}
+		wd := metadata.AbsPath(cwd)
+
+		manager, err := metadata.Find(wd)
+		if err != nil {
+			return err
+		}
+
+		app, err := manager.AppSpec()
+		if err != nil {
+			return err
+		}
+
+		regRef, exists := app.GetRegistryRef(name)
+		if !exists {
+			return fmt.Errorf("Registry '%s' doesn't exist", name)
+		}
+
+		reg, _, err := manager.GetRegistry(name)
+		if err != nil {
+			return err
+		}
+
+		fmt.Println(`REGISTRY NAME:`)
+		fmt.Println(regRef.Name)
+		fmt.Println()
+		fmt.Println(`URI:`)
+		fmt.Println(regRef.URI)
+		fmt.Println()
+		fmt.Println(`PROTOCOL:`)
+		fmt.Println(regRef.Protocol)
+		fmt.Println()
+		fmt.Println(`LIBRARIES:`)
+
+		for _, lib := range reg.Libraries {
+			fmt.Printf("  %s\n", lib.Path)
+		}
+
+		return nil
+	},
+
+	Long: `Output documentation for some ksonnet registry prototype uniquely identified in
+the current ksonnet project by some 'registry-name'.`,
+}
diff --git a/metadata/interface.go b/metadata/interface.go
index 0a07b4a499af8e470fb8240e5ba1b2d5eede80f3..b86c4983a78a00d25f2b3384d2fc548df8871967 100644
--- a/metadata/interface.go
+++ b/metadata/interface.go
@@ -75,7 +75,9 @@ type Manager interface {
 	// Dependency/registry API.
 	AddRegistry(name, protocol, uri, version string) (*registry.Spec, error)
 	GetRegistry(name string) (*registry.Spec, string, error)
+	GetPackage(registryName, libID string) (*parts.Spec, error)
 	CacheDependency(registryName, libID, libName, libVersion string) (*parts.Spec, error)
+	GetDependency(libName string) (*parts.Spec, error)
 	GetAllPrototypes() (prototype.SpecificationSchemas, error)
 }
 
diff --git a/metadata/parts/schema.go b/metadata/parts/schema.go
index 990d90650bd75b8693844209b83c9690223d92c6..c7f14ac16a9bc705ec1a55a50a8b05cb42751ffa 100644
--- a/metadata/parts/schema.go
+++ b/metadata/parts/schema.go
@@ -70,4 +70,4 @@ type QuickStartSpec struct {
 
 type Specs []*Spec
 
-type PrototypeRefSpecs map[string]string
+type PrototypeRefSpecs []string
diff --git a/metadata/registry.go b/metadata/registry.go
index 27cfddbb28b59489a7eeeece7864091956a3be60..24fb22e9554f6767bbded568972c00bfbf5ac8eb 100644
--- a/metadata/registry.go
+++ b/metadata/registry.go
@@ -73,6 +73,76 @@ func (m *manager) GetRegistry(name string) (*registry.Spec, string, error) {
 	return regSpec, protocol, nil
 }
 
+func (m *manager) GetPackage(registryName, libID string) (*parts.Spec, error) {
+	// Retrieve application specification.
+	appSpec, err := m.AppSpec()
+	if err != nil {
+		return nil, err
+	}
+
+	regRefSpec, ok := appSpec.GetRegistryRef(registryName)
+	if !ok {
+		return nil, fmt.Errorf("COuld not find registry '%s'", registryName)
+	}
+
+	registryManager, _, err := m.getRegistryManagerFor(regRefSpec)
+	if err != nil {
+		return nil, err
+	}
+
+	partsSpec, err := registryManager.ResolveLibrarySpec(libID, regRefSpec.GitVersion.CommitSHA)
+	if err != nil {
+		return nil, err
+	}
+
+	protoSpecs, err := m.GetPrototypesForDependency(registryName, libID)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, protoSpec := range protoSpecs {
+		partsSpec.Prototypes = append(partsSpec.Prototypes, protoSpec.Name)
+	}
+
+	return partsSpec, nil
+}
+
+func (m *manager) GetDependency(libName string) (*parts.Spec, error) {
+	// Retrieve application specification.
+	appSpec, err := m.AppSpec()
+	if err != nil {
+		return nil, err
+	}
+
+	libRef, ok := appSpec.Libraries[libName]
+	if !ok {
+		return nil, fmt.Errorf("Library '%s' is not a dependency in current ksonnet app", libName)
+	}
+
+	partsYAMLPath := appendToAbsPath(m.vendorPath, libRef.Registry, libName, partsYAMLFile)
+	partsBytes, err := afero.ReadFile(m.appFS, string(partsYAMLPath))
+	if err != nil {
+		return nil, err
+	}
+
+	var partsSpec parts.Spec
+	err = yaml.Unmarshal(partsBytes, &partsSpec)
+	if err != nil {
+		return nil, err
+	}
+
+	protoSpecs, err := m.GetPrototypesForDependency(libRef.Registry, libName)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, protoSpec := range protoSpecs {
+		partsSpec.Prototypes = append(partsSpec.Prototypes, protoSpec.Name)
+	}
+
+	return &partsSpec, nil
+}
+
 func (m *manager) CacheDependency(registryName, libID, libName, libVersion string) (*parts.Spec, error) {
 	// Retrieve application specification.
 	appSpec, err := m.AppSpec()
@@ -146,6 +216,44 @@ func (m *manager) CacheDependency(registryName, libID, libName, libVersion strin
 	return parts, nil
 }
 
+func (m *manager) GetPrototypesForDependency(registryName, libID string) (prototype.SpecificationSchemas, error) {
+	// TODO: Remove `registryName` when we flatten vendor/.
+	specs := prototype.SpecificationSchemas{}
+	protos := string(appendToAbsPath(m.vendorPath, registryName, libID, "prototypes"))
+	exists, err := afero.DirExists(m.appFS, protos)
+	if err != nil {
+		return nil, err
+	} else if !exists {
+		return prototype.SpecificationSchemas{}, nil // No prototypes to report.
+	}
+
+	err = afero.Walk(
+		m.appFS,
+		protos,
+		func(path string, info os.FileInfo, err error) error {
+			if info.IsDir() || filepath.Ext(path) != ".jsonnet" {
+				return nil
+			}
+
+			protoJsonnet, err := afero.ReadFile(m.appFS, path)
+			if err != nil {
+				return err
+			}
+
+			protoSpec, err := prototype.FromJsonnet(string(protoJsonnet))
+			if err != nil {
+				return err
+			}
+			specs = append(specs, protoSpec)
+			return nil
+		})
+	if err != nil {
+		return nil, err
+	}
+
+	return specs, nil
+}
+
 func (m *manager) GetAllPrototypes() (prototype.SpecificationSchemas, error) {
 	appSpec, err := m.AppSpec()
 	if err != nil {
@@ -154,37 +262,11 @@ func (m *manager) GetAllPrototypes() (prototype.SpecificationSchemas, error) {
 
 	specs := prototype.SpecificationSchemas{}
 	for _, lib := range appSpec.Libraries {
-		protos := string(appendToAbsPath(m.vendorPath, lib.Registry, lib.Name, "prototypes"))
-		exists, err := afero.DirExists(m.appFS, protos)
-		if err != nil {
-			return nil, err
-		} else if !exists {
-			return prototype.SpecificationSchemas{}, nil // No prototypes to report.
-		}
-
-		err = afero.Walk(
-			m.appFS,
-			protos,
-			func(path string, info os.FileInfo, err error) error {
-				if info.IsDir() || filepath.Ext(path) != ".jsonnet" {
-					return nil
-				}
-
-				protoJsonnet, err := afero.ReadFile(m.appFS, path)
-				if err != nil {
-					return err
-				}
-
-				protoSpec, err := prototype.FromJsonnet(string(protoJsonnet))
-				if err != nil {
-					return err
-				}
-				specs = append(specs, protoSpec)
-				return nil
-			})
+		depProtos, err := m.GetPrototypesForDependency(lib.Registry, lib.Name)
 		if err != nil {
 			return nil, err
 		}
+		specs = append(specs, depProtos...)
 	}
 
 	return specs, nil
diff --git a/metadata/registry/interface.go b/metadata/registry/interface.go
index 416da670df296555dd4416e4a86158bb088890e5..6a3457379f2aee59dc0b9655384259b297108b83 100644
--- a/metadata/registry/interface.go
+++ b/metadata/registry/interface.go
@@ -13,5 +13,6 @@ type Manager interface {
 	RegistrySpecFilePath() string
 	FetchRegistrySpec() (*Spec, error)
 	MakeRegistryRefSpec() *app.RegistryRefSpec
+	ResolveLibrarySpec(libID, libRefSpec string) (*parts.Spec, error)
 	ResolveLibrary(libID, libAlias, version string, onFile ResolveFile, onDir ResolveDirectory) (*parts.Spec, *app.LibraryRefSpec, error)
 }
diff --git a/metadata/registry_managers.go b/metadata/registry_managers.go
index 78f53736fd947ccce0ff0ae23e0f12ca7974688f..9ea7645a1eca307ebfa13d0ebb7d1dbce788ecb7 100644
--- a/metadata/registry_managers.go
+++ b/metadata/registry_managers.go
@@ -116,6 +116,41 @@ func (gh *gitHubRegistryManager) MakeRegistryRefSpec() *app.RegistryRefSpec {
 	return gh.RegistryRefSpec
 }
 
+func (gh *gitHubRegistryManager) ResolveLibrarySpec(libID, libRefSpec string) (*parts.Spec, error) {
+	client := github.NewClient(nil)
+
+	// Resolve `version` (a git refspec) to a specific SHA.
+	ctx := context.Background()
+	resolvedSHA, _, err := client.Repositories.GetCommitSHA1(ctx, gh.org, gh.repo, libRefSpec, "")
+	if err != nil {
+		return nil, err
+	}
+
+	// Resolve app spec.
+	appSpecPath := strings.Join([]string{gh.registryRepoPath, libID, partsYAMLFile}, "/")
+	ctx = context.Background()
+	getOpts := &github.RepositoryContentGetOptions{Ref: resolvedSHA}
+	file, directory, _, err := client.Repositories.GetContents(ctx, gh.org, gh.repo, appSpecPath, getOpts)
+	if err != nil {
+		return nil, err
+	} else if directory != nil {
+		return nil, fmt.Errorf("Can't download library specification; resource '%s' points at a file", gh.registrySpecRawURL())
+	}
+
+	partsSpecText, err := file.GetContent()
+	if err != nil {
+		return nil, err
+	}
+
+	parts := parts.Spec{}
+	err = yaml.Unmarshal([]byte(partsSpecText), &parts)
+	if err != nil {
+		return nil, err
+	}
+
+	return &parts, nil
+}
+
 func (gh *gitHubRegistryManager) ResolveLibrary(libID, libAlias, libRefSpec string, onFile registry.ResolveFile, onDir registry.ResolveDirectory) (*parts.Spec, *app.LibraryRefSpec, error) {
 	client := github.NewClient(nil)
 
@@ -150,7 +185,10 @@ func (gh *gitHubRegistryManager) ResolveLibrary(libID, libAlias, libRefSpec stri
 	}
 
 	parts := parts.Spec{}
-	yaml.Unmarshal([]byte(partsSpecText), &parts)
+	err = yaml.Unmarshal([]byte(partsSpecText), &parts)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	refSpec := app.LibraryRefSpec{
 		Name:     libAlias,
diff --git a/metadata/registry_test.go b/metadata/registry_test.go
index e7e8a81d3eec6da7e348953d9852b1ceb2a417ac..8248681e682625127aaf0c1fb11375f9651d7e9e 100644
--- a/metadata/registry_test.go
+++ b/metadata/registry_test.go
@@ -22,6 +22,10 @@ func newMockRegistryManager(name string) *mockRegistryManager {
 	}
 }
 
+func (m *mockRegistryManager) ResolveLibrarySpec(libID, libRefSpec string) (*parts.Spec, error) {
+	return nil, nil
+}
+
 func (m *mockRegistryManager) RegistrySpecDir() string {
 	return m.registryDir
 }