Detailed changes
  
  
    
    @@ -71,6 +71,12 @@ func Targets() []string {
 	return result
 }
 
+// TargetExist return true if the given target has a bridge implementation
+func TargetExist(target string) bool {
+	_, ok := bridgeImpl[target]
+	return ok
+}
+
 // Instantiate a new Bridge for a repo, from the given target and name
 func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) {
 	implType, ok := bridgeImpl[target]
  
  
  
    
    @@ -2,14 +2,13 @@ package core
 
 import (
 	"crypto/sha256"
+	"errors"
 	"fmt"
 	"regexp"
-	"strconv"
+	"sort"
 	"strings"
 	"time"
 
-	"github.com/araddon/dateparse"
-
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
@@ -21,9 +20,14 @@ const (
 	tokenCreateTimeKey   = "createtime"
 )
 
+var ErrTokenNotExist = errors.New("token doesn't exist")
+
+func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch {
+	return entity.NewErrMultipleMatch("token", matching)
+}
+
 // Token holds an API access token data
 type Token struct {
-	ID         entity.Id
 	Value      string
 	Target     string
 	CreateTime time.Time
@@ -31,86 +35,96 @@ type Token struct {
 
 // NewToken instantiate a new token
 func NewToken(value, target string) *Token {
-	token := &Token{
+	return &Token{
 		Value:      value,
 		Target:     target,
 		CreateTime: time.Now(),
 	}
-
-	token.ID = entity.Id(hashToken(token))
-	return token
 }
 
-func hashToken(token *Token) string {
-	sum := sha256.Sum256([]byte(token.Value))
-	return fmt.Sprintf("%x", sum)
+func (t *Token) ID() entity.Id {
+	sum := sha256.Sum256([]byte(t.Value))
+	return entity.Id(fmt.Sprintf("%x", sum))
 }
 
 // Validate ensure token important fields are valid
 func (t *Token) Validate() error {
-	if t.ID == "" {
-		return fmt.Errorf("missing id")
-	}
 	if t.Value == "" {
 		return fmt.Errorf("missing value")
 	}
 	if t.Target == "" {
 		return fmt.Errorf("missing target")
 	}
-	if t.CreateTime.Equal(time.Time{}) {
+	if t.CreateTime.IsZero() {
 		return fmt.Errorf("missing creation time")
 	}
-	if _, ok := bridgeImpl[t.Target]; !ok {
+	if !TargetExist(t.Target) {
 		return fmt.Errorf("unknown target")
 	}
 	return nil
 }
 
-// LoadToken loads a token from repo config
-func LoadToken(repo repository.RepoCommon, id string) (*Token, error) {
+// LoadToken loads a token from the repo config
+func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) {
 	keyPrefix := fmt.Sprintf("git-bug.token.%s.", id)
 
 	// read token config pairs
-	configs, err := repo.GlobalConfig().ReadAll(keyPrefix)
+	rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
 	if err != nil {
-		return nil, err
+		// Not exactly right due to the limitation of ReadAll()
+		return nil, ErrTokenNotExist
 	}
 
 	// trim key prefix
-	for key, value := range configs {
-		delete(configs, key)
+	configs := make(map[string]string)
+	for key, value := range rawconfigs {
 		newKey := strings.TrimPrefix(key, keyPrefix)
 		configs[newKey] = value
 	}
 
-	token := &Token{ID: entity.Id(id)}
+	token := &Token{}
+
+	token.Value = configs[tokenValueKey]
+	token.Target = configs[tokenTargetKey]
+	if createTime, ok := configs[tokenCreateTimeKey]; ok {
+		if t, err := repository.ParseTimestamp(createTime); err == nil {
+			token.CreateTime = t
+		}
+	}
+
+	return token, nil
+}
 
-	var ok bool
-	token.Value, ok = configs[tokenValueKey]
-	if !ok {
-		return nil, fmt.Errorf("empty token value")
+// LoadTokenPrefix load a token from the repo config with a prefix
+func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) {
+	tokens, err := ListTokens(repo)
+	if err != nil {
+		return nil, err
 	}
 
-	token.Target, ok = configs[tokenTargetKey]
-	if !ok {
-		return nil, fmt.Errorf("empty token key")
+	// preallocate but empty
+	matching := make([]entity.Id, 0, 5)
+
+	for _, id := range tokens {
+		if id.HasPrefix(prefix) {
+			matching = append(matching, id)
+		}
 	}
 
-	createTime, ok := configs[tokenCreateTimeKey]
-	if !ok {
-		return nil, fmt.Errorf("missing createtime key")
+	if len(matching) > 1 {
+		return nil, NewErrMultipleMatchToken(matching)
 	}
 
-	token.CreateTime, err = dateparse.ParseLocal(createTime)
-	if err != nil {
-		return nil, err
+	if len(matching) == 0 {
+		return nil, ErrTokenNotExist
 	}
-	return token, nil
+
+	return LoadToken(repo, matching[0])
 }
 
 // ListTokens return a map representing the stored tokens in the repo config and global config
 // along with their type (global: true, local:false)
-func ListTokens(repo repository.RepoCommon) ([]string, error) {
+func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) {
 	configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".")
 	if err != nil {
 		return nil, err
@@ -133,36 +147,36 @@ func ListTokens(repo repository.RepoCommon) ([]string, error) {
 		set[res[1]] = nil
 	}
 
-	result := make([]string, len(set))
-	i := 0
+	result := make([]entity.Id, 0, len(set))
 	for key := range set {
-		result[i] = key
-		i++
+		result = append(result, entity.Id(key))
 	}
 
+	sort.Sort(entity.Alphabetical(result))
+
 	return result, nil
 }
 
 // StoreToken stores a token in the repo config
 func StoreToken(repo repository.RepoCommon, token *Token) error {
-	storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenValueKey)
+	storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey)
 	err := repo.GlobalConfig().StoreString(storeValueKey, token.Value)
 	if err != nil {
 		return err
 	}
 
-	storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenTargetKey)
+	storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey)
 	err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target)
 	if err != nil {
 		return err
 	}
 
-	createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID.String(), tokenCreateTimeKey)
-	return repo.GlobalConfig().StoreString(createTimeKey, strconv.Itoa(int(token.CreateTime.Unix())))
+	createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey)
+	return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime)
 }
 
 // RemoveToken removes a token from the repo config
-func RemoveToken(repo repository.RepoCommon, id string) error {
+func RemoveToken(repo repository.RepoCommon, id entity.Id) error {
 	keyPrefix := fmt.Sprintf("git-bug.token.%s", id)
 	return repo.GlobalConfig().RemoveAll(keyPrefix)
 }
  
  
  
    
    @@ -6,27 +6,14 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/bridge/core"
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/colors"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 	text "github.com/MichaelMure/go-term-text"
-)
 
-var (
-	bridgeTokenLocal  bool
-	bridgeTokenGlobal bool
+	"github.com/MichaelMure/git-bug/bridge/core"
+	"github.com/MichaelMure/git-bug/util/colors"
 )
 
 func runTokenBridge(cmd *cobra.Command, args []string) error {
-	backend, err := cache.NewRepoCache(repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	tokens, err := core.ListTokens(backend)
+	tokens, err := core.ListTokens(repo)
 	if err != nil {
 		return err
 	}
@@ -43,13 +30,12 @@ func runTokenBridge(cmd *cobra.Command, args []string) error {
 }
 
 func printToken(token *core.Token) {
-	idFmt := text.LeftPadMaxLine(token.ID.Human(), 7, 0)
 	valueFmt := text.LeftPadMaxLine(token.Value, 15, 0)
 	targetFmt := text.LeftPadMaxLine(token.Target, 7, 0)
 	createTimeFmt := text.LeftPadMaxLine(token.CreateTime.Format(time.RFC822), 20, 0)
 
 	fmt.Printf("%s %s %s %s\n",
-		idFmt,
+		token.ID().Human(),
 		colors.Magenta(targetFmt),
 		valueFmt,
 		createTimeFmt,
@@ -58,7 +44,7 @@ func printToken(token *core.Token) {
 
 var bridgeTokenCmd = &cobra.Command{
 	Use:     "token",
-	Short:   "List all stored tokens.",
+	Short:   "List all known tokens.",
 	PreRunE: loadRepo,
 	RunE:    runTokenBridge,
 	Args:    cobra.NoArgs,
@@ -66,7 +52,5 @@ var bridgeTokenCmd = &cobra.Command{
 
 func init() {
 	bridgeCmd.AddCommand(bridgeTokenCmd)
-	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenLocal, "local", "l", false, "")
-	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenGlobal, "global", "g", false, "")
 	bridgeTokenCmd.Flags().SortFlags = false
 }
  
  
  
    
    @@ -1,37 +1,74 @@
 package commands
 
 import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/mattn/go-isatty"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 
+	"github.com/MichaelMure/git-bug/bridge"
 	"github.com/MichaelMure/git-bug/bridge/core"
 )
 
 var (
-	bridgeTokenValue  string
 	bridgeTokenTarget string
 )
 
 func runBridgeTokenAdd(cmd *cobra.Command, args []string) error {
-	token := core.NewToken(bridgeTokenValue, bridgeTokenTarget)
+	var value string
+
+	if bridgeTokenTarget == "" {
+		return fmt.Errorf("token target is required")
+	}
+
+	if !core.TargetExist(bridgeTokenTarget) {
+		return fmt.Errorf("unknown target")
+	}
+
+	if len(args) == 1 {
+		value = args[0]
+	} else {
+		// Read from Stdin
+		if isatty.IsTerminal(os.Stdin.Fd()) {
+			fmt.Println("Enter the token:")
+		}
+		reader := bufio.NewReader(os.Stdin)
+		raw, err := reader.ReadString('\n')
+		if err != nil {
+			return fmt.Errorf("reading from stdin: %v", err)
+		}
+		value = strings.TrimSuffix(raw, "\n")
+	}
+
+	token := core.NewToken(value, bridgeTokenTarget)
 	if err := token.Validate(); err != nil {
 		return errors.Wrap(err, "invalid token")
 	}
 
-	return core.StoreToken(repo, token)
+	err := core.StoreToken(repo, token)
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("token %s added\n", token.ID())
+	return nil
 }
 
 var bridgeTokenAddCmd = &cobra.Command{
 	Use:     "add",
-	Short:   "Create and store a new token",
+	Short:   "Store a new token",
 	PreRunE: loadRepo,
 	RunE:    runBridgeTokenAdd,
-	Args:    cobra.NoArgs,
+	Args:    cobra.MaximumNArgs(1),
 }
 
 func init() {
 	bridgeTokenCmd.AddCommand(bridgeTokenAddCmd)
-	bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenValue, "value", "v", "", "")
-	bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenTarget, "target", "t", "", "")
+	bridgeTokenAddCmd.Flags().StringVarP(&bridgeTokenTarget, "target", "t", "",
+		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
 	bridgeTokenAddCmd.Flags().SortFlags = false
 }
  
  
  
    
    @@ -1,13 +1,26 @@
 package commands
 
 import (
+	"fmt"
+
 	"github.com/spf13/cobra"
 
 	"github.com/MichaelMure/git-bug/bridge/core"
 )
 
 func runBridgeTokenRm(cmd *cobra.Command, args []string) error {
-	return core.RemoveToken(repo, args[0])
+	token, err := core.LoadTokenPrefix(repo, args[0])
+	if err != nil {
+		return err
+	}
+
+	err = core.RemoveToken(repo, token.ID())
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("token %s removed\n", token.ID())
+	return nil
 }
 
 var bridgeTokenRmCmd = &cobra.Command{
  
  
  
    
    @@ -5,7 +5,7 @@
 
 .SH NAME
 .PP
-git\-bug\-bridge\-token\-add \- Create and store a new token
+git\-bug\-bridge\-token\-add \- Store a new token
 
 
 .SH SYNOPSIS
@@ -15,15 +15,13 @@ git\-bug\-bridge\-token\-add \- Create and store a new token
 
 .SH DESCRIPTION
 .PP
-Create and store a new token
+Store a new token
 
 
 .SH OPTIONS
-.PP
-\fB\-v\fP, \fB\-\-value\fP=""
-
 .PP
 \fB\-t\fP, \fB\-\-target\fP=""
+    The target of the bridge. Valid values are [github,gitlab,launchpad\-preview]
 
 .PP
 \fB\-h\fP, \fB\-\-help\fP[=false]
  
  
  
    
    @@ -5,7 +5,7 @@
 
 .SH NAME
 .PP
-git\-bug\-bridge\-token \- List all stored tokens.
+git\-bug\-bridge\-token \- List all known tokens.
 
 
 .SH SYNOPSIS
@@ -15,16 +15,10 @@ git\-bug\-bridge\-token \- List all stored tokens.
 
 .SH DESCRIPTION
 .PP
-List all stored tokens.
+List all known tokens.
 
 
 .SH OPTIONS
-.PP
-\fB\-l\fP, \fB\-\-local\fP[=false]
-
-.PP
-\fB\-g\fP, \fB\-\-global\fP[=false]
-
 .PP
 \fB\-h\fP, \fB\-\-help\fP[=false]
     help for token
  
  
  
    
    @@ -23,5 +23,5 @@ git-bug bridge [flags]
 * [git-bug bridge pull](git-bug_bridge_pull.md)	 - Pull updates.
 * [git-bug bridge push](git-bug_bridge_push.md)	 - Push updates.
 * [git-bug bridge rm](git-bug_bridge_rm.md)	 - Delete a configured bridge.
-* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all known tokens.
 
  
  
  
    
    @@ -1,10 +1,10 @@
 ## git-bug bridge token
 
-List all stored tokens.
+List all known tokens.
 
 ### Synopsis
 
-List all stored tokens.
+List all known tokens.
 
 ```
 git-bug bridge token [flags]
@@ -13,14 +13,12 @@ git-bug bridge token [flags]
 ### Options
 
 ```
-  -l, --local    
-  -g, --global   
-  -h, --help     help for token
+  -h, --help   help for token
 ```
 
 ### SEE ALSO
 
 * [git-bug bridge](git-bug_bridge.md)	 - Configure and use bridges to other bug trackers.
-* [git-bug bridge token add](git-bug_bridge_token_add.md)	 - Create and store a new token
+* [git-bug bridge token add](git-bug_bridge_token_add.md)	 - Store a new token
 * [git-bug bridge token rm](git-bug_bridge_token_rm.md)	 - Remove token by Id.
 
  
  
  
    
    @@ -1,10 +1,10 @@
 ## git-bug bridge token add
 
-Create and store a new token
+Store a new token
 
 ### Synopsis
 
-Create and store a new token
+Store a new token
 
 ```
 git-bug bridge token add [flags]
@@ -13,12 +13,11 @@ git-bug bridge token add [flags]
 ### Options
 
 ```
-  -v, --value string    
-  -t, --target string   
+  -t, --target string   The target of the bridge. Valid values are [github,gitlab,launchpad-preview]
   -h, --help            help for add
 ```
 
 ### SEE ALSO
 
-* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all known tokens.
 
  
  
  
    
    @@ -18,5 +18,5 @@ git-bug bridge token rm [flags]
 
 ### SEE ALSO
 
-* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all known tokens.
 
  
  
  
    
    @@ -65,3 +65,21 @@ func (i Id) Validate() error {
 	}
 	return nil
 }
+
+/*
+ * Sorting
+ */
+
+type Alphabetical []Id
+
+func (a Alphabetical) Len() int {
+	return len(a)
+}
+
+func (a Alphabetical) Less(i, j int) bool {
+	return a[i] < a[j]
+}
+
+func (a Alphabetical) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
  
  
  
    
    @@ -414,10 +414,6 @@ _git-bug_bridge_token_add()
     flags_with_completion=()
     flags_completion=()
 
-    flags+=("--value=")
-    two_word_flags+=("--value")
-    two_word_flags+=("-v")
-    local_nonpersistent_flags+=("--value=")
     flags+=("--target=")
     two_word_flags+=("--target")
     two_word_flags+=("-t")
@@ -464,12 +460,6 @@ _git-bug_bridge_token()
     flags_with_completion=()
     flags_completion=()
 
-    flags+=("--local")
-    flags+=("-l")
-    local_nonpersistent_flags+=("--local")
-    flags+=("--global")
-    flags+=("-g")
-    local_nonpersistent_flags+=("--global")
 
     must_have_one_flag=()
     must_have_one_noun=()
  
  
  
    
    @@ -52,7 +52,7 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             [CompletionResult]::new('pull', 'pull', [CompletionResultType]::ParameterValue, 'Pull updates.')
             [CompletionResult]::new('push', 'push', [CompletionResultType]::ParameterValue, 'Push updates.')
             [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Delete a configured bridge.')
-            [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'List all stored tokens.')
+            [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'List all known tokens.')
             break
         }
         'git-bug;bridge;configure' {
@@ -85,19 +85,13 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             break
         }
         'git-bug;bridge;token' {
-            [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('--local', 'local', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('--global', 'global', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Create and store a new token')
+            [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Store a new token')
             [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove token by Id.')
             break
         }
         'git-bug;bridge;token;add' {
-            [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('--value', 'value', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, '')
+            [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]')
+            [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]')
             break
         }
         'git-bug;bridge;token;rm' {
  
  
  
    
    @@ -118,7 +118,7 @@ function _git-bug_bridge {
       "pull:Pull updates."
       "push:Push updates."
       "rm:Delete a configured bridge."
-      "token:List all stored tokens."
+      "token:List all known tokens."
     )
     _describe "command" commands
     ;;
@@ -173,15 +173,13 @@ function _git-bug_bridge_token {
   local -a commands
 
   _arguments -C \
-    '(-l --local)'{-l,--local}'[]' \
-    '(-g --global)'{-g,--global}'[]' \
     "1: :->cmnds" \
     "*::arg:->args"
 
   case $state in
   cmnds)
     commands=(
-      "add:Create and store a new token"
+      "add:Store a new token"
       "rm:Remove token by Id."
     )
     _describe "command" commands
@@ -200,8 +198,7 @@ function _git-bug_bridge_token {
 
 function _git-bug_bridge_token_add {
   _arguments \
-    '(-v --value)'{-v,--value}'[]:' \
-    '(-t --target)'{-t,--target}'[]:'
+    '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,gitlab,launchpad-preview]]:'
 }
 
 function _git-bug_bridge_token_rm {
  
  
  
    
    @@ -38,7 +38,7 @@ type Config interface {
 	RemoveAll(keyPrefix string) error
 }
 
-func parseTimestamp(s string) (time.Time, error) {
+func ParseTimestamp(s string) (time.Time, error) {
 	timestamp, err := strconv.Atoi(s)
 	if err != nil {
 		return time.Time{}, err
  
  
  
    
    @@ -116,7 +116,7 @@ func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) {
 	if err != nil {
 		return time.Time{}, err
 	}
-	return parseTimestamp(value)
+	return ParseTimestamp(value)
 }
 
 func (gc *gitConfig) rmSection(keyPrefix string) error {