コード例 #1
0
ファイル: wallet_history.go プロジェクト: skycoin/skycoin
func walletHistoryAction(c *gcli.Context) error {
	if c.NArg() > 0 {
		fmt.Printf("Error: invalid argument\n\n")
		gcli.ShowSubcommandHelp(c)
		return nil
	}
	f := c.String("f")
	if f == "" {
		f = filepath.Join(cfg.WalletDir, cfg.DefaultWalletName)
	}

	// check the file extension.
	if !strings.HasSuffix(f, walletExt) {
		return errWalletName
	}

	// check if file name contains path.
	if filepath.Base(f) != f {
		af, err := filepath.Abs(f)
		if err != nil {
			return fmt.Errorf("invalid wallet file:%v, err:%v", f, err)
		}
		f = af
	} else {
		f = filepath.Join(cfg.WalletDir, f)
	}

	// get all addresses in the wallet.
	addrs, err := getAddresses(f)
	if err != nil {
		return err
	}

	// get all the addresses affected uxouts
	uxouts, err := getAddrUxOuts(addrs)
	if err != nil {
		return err
	}

	// transmute the uxout to addrHistory, and sort the items by time in ascend order.
	totalAddrHis := []addrHistory{}
	for _, ux := range uxouts {
		addrHis, err := makeAddrHisArray(ux)
		if err != nil {
			return err
		}
		totalAddrHis = append(totalAddrHis, addrHis...)
	}

	sort.Sort(byTime(totalAddrHis))

	// print the addr history
	v, err := json.MarshalIndent(totalAddrHis, "", "    ")
	if err != nil {
		return errJSONMarshal
	}
	fmt.Println(string(v))
	return nil
}
コード例 #2
0
ファイル: shell.go プロジェクト: itpkg/chaos
func write_config(c *cli.Context, files map[string]string) error {
	user := c.String("user")
	password := c.String("password")
	if len(user) == 0 || len(password) == 0 {
		cli.ShowSubcommandHelp(c)
		return nil
	}
	for n, b := range files {
		if err := os.MkdirAll(path.Dir(n), 0700); err != nil {
			return err
		}
		fmt.Printf("generate file %s\n", n)
		fd, err := os.OpenFile(
			n,
			os.O_WRONLY|os.O_CREATE|os.O_EXCL,
			0600)
		if err != nil {
			return err
		}
		defer fd.Close()
		t, err := template.New("").Parse(b)
		if err != nil {
			return err
		}
		driver := viper.GetString("database.driver")
		switch driver {
		case "postgres":
			driver = "pgsql"
		}
		args := viper.GetStringMapString("database.args")
		if err = t.Execute(fd, struct {
			Driver      string
			User        string
			Password    string
			Name        string
			Host        string
			Port        string
			UserTable   string
			AliasTable  string
			DomainTable string
		}{
			Driver:      driver,
			Host:        args["host"],
			Name:        args["dbname"],
			Port:        args["port"],
			Password:    password,
			User:        user,
			UserTable:   User{}.TableName(),
			AliasTable:  Alias{}.TableName(),
			DomainTable: Domain{}.TableName(),
		}); err != nil {
			return err
		}
	}

	return nil
}
コード例 #3
0
ファイル: blocks.go プロジェクト: skycoin/skycoin
func getBlocks(c *gcli.Context) error {
	// get start
	start := c.Args().Get(0)
	end := c.Args().Get(1)
	if end == "" {
		end = start
	}

	if start == "" {
		gcli.ShowSubcommandHelp(c)
		return nil
	}

	s, err := strconv.ParseUint(start, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid block seq: %v, must be unsigned integer", start)
	}

	e, err := strconv.ParseUint(end, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid block seq: %v, must be unsigned integer", end)
	}

	param := []uint64{s, e}

	req, err := webrpc.NewRequest("get_blocks", param, "1")
	if err != nil {
		return fmt.Errorf("create rpc request failed: %v", err)
	}

	rsp, err := webrpc.Do(req, cfg.RPCAddress)
	if err != nil {
		return fmt.Errorf("do rpc request failed: %v", err)
	}

	if rsp.Error != nil {
		return fmt.Errorf("rpc response error: %+v", *rsp.Error)
	}

	fmt.Println(string(rsp.Result))
	return nil
}
コード例 #4
0
ファイル: command-validate.go プロジェクト: fritzing/fzp
func commandValidateAction(c *cli.Context) error {
	fmt.Println("validate fzp file")
	// get cli flag value
	fzpFile := c.String("file")
	fzpDir := c.String("dir")

	// process data
	if fzpFile != "" {
		if err := validateFile(c, fzpFile); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		os.Exit(0)
	} else if fzpDir != "" {
		//Logf("read folder '%v'\n", fzpDir)
		var err []error
		if err = validateFolder(c, fzpDir); err != nil {
			os.Exit(1)
		}
		os.Exit(0)
	} else {
		cli.ShowSubcommandHelp(c)
		fmt.Println("USAGE-SAMPLES")
		fmt.Println("")
		fmt.Printf("   $ %v validate --file file/path.fzp\n", c.App.Name)
		fmt.Println("   # or")
		fmt.Printf("   $ %v validate -f file/path.fzp\n", c.App.Name)
		fmt.Println("")
		fmt.Printf("   $ %v validate --dir file/dir\n", c.App.Name)
		fmt.Println("   # or")
		fmt.Printf("   $ %v validate -d file/dir\n", c.App.Name)
		fmt.Println("")
		fmt.Println("also you can combine the other flags (no-check, verbose)")
	}
	return nil
}
コード例 #5
0
ファイル: broadcast_rawtx.go プロジェクト: skycoin/skycoin
func broadcastTxCMD() gcli.Command {
	name := "broadcastTransaction"
	return gcli.Command{
		Name:         name,
		Usage:        "Broadcast a raw transaction to the network",
		ArgsUsage:    "[raw transaction]",
		OnUsageError: onCommandUsageError(name),
		Action: func(c *gcli.Context) error {
			rawtx := c.Args().First()
			if rawtx == "" {
				gcli.ShowSubcommandHelp(c)
				return nil
			}
			txid, err := broadcastTx(rawtx)
			if err != nil {
				return err
			}

			fmt.Println(txid)
			return nil
		},
	}
	// Commands = append(Commands, cmd)
}
コード例 #6
0
ファイル: appFactory.go プロジェクト: echocat/caretakerd
func newAppFor(executableType ExecutableType) *cli.App {
	var configDescription string
	var configEnvVar string
	switch executableType {
	case Daemon:
		configDescription = "Configuration file for daemon."
		configEnvVar = "CTD_CONFIG"
	case Control:
		configDescription = "Configuration file for control."
		configEnvVar = "CTCTL_CONFIG"
	default:
		configDescription = "Configuration file for daemon and control."
		configEnvVar = "CT_CONFIG"
	}

	app := cli.NewApp()
	app.Version = caretakerd.Version
	app.Commands = []cli.Command{}
	app.OnUsageError = func(context *cli.Context, err error, isSubcommand bool) error {
		fmt.Fprintf(app.Writer, "Error: %v\n\n", err)
		if isSubcommand {
			cli.ShowSubcommandHelp(context)
		} else {
			cli.ShowAppHelp(context)
		}
		return err
	}
	app.Flags = []cli.Flag{
		cli.GenericFlag{
			Name:   "config,c",
			Value:  conf,
			Usage:  configDescription,
			EnvVar: configEnvVar,
		},
		cli.GenericFlag{
			Name:  "address,a",
			Value: listenAddress,
			Usage: "Listen address of the daemon.",
		},
	}

	if executableType != Daemon {
		app.Flags = append(app.Flags, cli.GenericFlag{
			Name:  "pem,p",
			Value: pemFile,
			Usage: "Location of PEM file which contains the private public key pair for access to the daemon.",
		})
	}

	switch executableType {
	case Daemon:
		app.Name = caretakerd.DaemonName
		app.Usage = "Simple control daemon for processes."
	case Control:
		app.Name = caretakerd.ControlName
		app.Usage = "Remote control for " + caretakerd.DaemonName
	default:
		app.Name = caretakerd.BaseName
		app.Usage = "Simple control daemon for processes including remote control for itself."
	}
	return app
}
コード例 #7
0
ファイル: main.go プロジェクト: CodyGuo/Go-Cody
func main() {
	app := cli.NewApp()
	app.Name = "kənˈtrīv"
	app.Version = "19.99.0"
	app.Compiled = time.Now()
	app.Authors = []cli.Author{
		cli.Author{
			Name:  "Example Human",
			Email: "*****@*****.**",
		},
	}
	app.Copyright = "(c) 1999 Serious Enterprise"
	app.HelpName = "contrive"
	app.Usage = "demonstrate available API"
	app.UsageText = "contrive - demonstrating the available API"
	app.ArgsUsage = "[args and such]"
	app.Commands = []cli.Command{
		cli.Command{
			Name:        "doo",
			Aliases:     []string{"do"},
			Category:    "motion",
			Usage:       "do the doo",
			UsageText:   "doo - does the dooing",
			Description: "no really, there is a lot of dooing to be done",
			ArgsUsage:   "[arrgh]",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "forever, forevvarr"},
			},
			Subcommands: cli.Commands{
				cli.Command{
					Name:   "wop",
					Action: wopAction,
				},
			},
			SkipFlagParsing: false,
			HideHelp:        false,
			Hidden:          false,
			HelpName:        "doo!",
			BashComplete: func(c *cli.Context) {
				fmt.Fprintf(c.App.Writer, "--better\n")
			},
			Before: func(c *cli.Context) error {
				fmt.Fprintf(c.App.Writer, "brace for impact\n")
				return nil
			},
			After: func(c *cli.Context) error {
				fmt.Fprintf(c.App.Writer, "did we lose anyone?\n")
				return nil
			},
			Action: func(c *cli.Context) error {
				c.Command.FullName()
				c.Command.HasName("wop")
				c.Command.Names()
				c.Command.VisibleFlags()
				fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n")
				if c.Bool("forever") {
					c.Command.Run(c)
				}
				return nil
			},
			OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error {
				fmt.Fprintf(c.App.Writer, "for shame\n")
				return err
			},
		},
	}
	app.Flags = []cli.Flag{
		cli.BoolFlag{Name: "fancy"},
		cli.BoolTFlag{Name: "fancier"},
		cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3},
		cli.Float64Flag{Name: "howmuch"},
		cli.GenericFlag{Name: "wat", Value: &genericType{}},
		cli.Int64Flag{Name: "longdistance"},
		cli.Int64SliceFlag{Name: "intervals"},
		cli.IntFlag{Name: "distance"},
		cli.IntSliceFlag{Name: "times"},
		cli.StringFlag{Name: "dance-move, d"},
		cli.StringSliceFlag{Name: "names, N"},
		cli.UintFlag{Name: "age"},
		cli.Uint64Flag{Name: "bigage"},
	}
	app.EnableBashCompletion = true
	app.HideHelp = false
	app.HideVersion = false
	app.BashComplete = func(c *cli.Context) {
		fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n")
	}
	app.Before = func(c *cli.Context) error {
		fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n")
		return nil
	}
	app.After = func(c *cli.Context) error {
		fmt.Fprintf(c.App.Writer, "Phew!\n")
		return nil
	}
	app.CommandNotFound = func(c *cli.Context, command string) {
		fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command)
	}
	app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error {
		if isSubcommand {
			return err
		}

		fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err)
		return nil
	}
	app.Action = func(c *cli.Context) error {
		cli.DefaultAppComplete(c)
		cli.HandleExitCoder(errors.New("not an exit coder, though"))
		cli.ShowAppHelp(c)
		cli.ShowCommandCompletions(c, "nope")
		cli.ShowCommandHelp(c, "also-nope")
		cli.ShowCompletions(c)
		cli.ShowSubcommandHelp(c)
		cli.ShowVersion(c)

		categories := c.App.Categories()
		categories.AddCommand("sounds", cli.Command{
			Name: "bloop",
		})

		for _, category := range c.App.Categories() {
			fmt.Fprintf(c.App.Writer, "%s\n", category.Name)
			fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands)
			fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands())
		}

		fmt.Printf("%#v\n", c.App.Command("doo"))
		if c.Bool("infinite") {
			c.App.Run([]string{"app", "doo", "wop"})
		}

		if c.Bool("forevar") {
			c.App.RunAsSubcommand(c)
		}
		c.App.Setup()
		fmt.Printf("%#v\n", c.App.VisibleCategories())
		fmt.Printf("%#v\n", c.App.VisibleCommands())
		fmt.Printf("%#v\n", c.App.VisibleFlags())

		fmt.Printf("%#v\n", c.Args().First())
		if len(c.Args()) > 0 {
			fmt.Printf("%#v\n", c.Args()[1])
		}
		fmt.Printf("%#v\n", c.Args().Present())
		fmt.Printf("%#v\n", c.Args().Tail())

		set := flag.NewFlagSet("contrive", 0)
		nc := cli.NewContext(c.App, set, c)

		fmt.Printf("%#v\n", nc.Args())
		fmt.Printf("%#v\n", nc.Bool("nope"))
		fmt.Printf("%#v\n", nc.BoolT("nerp"))
		fmt.Printf("%#v\n", nc.Duration("howlong"))
		fmt.Printf("%#v\n", nc.Float64("hay"))
		fmt.Printf("%#v\n", nc.Generic("bloop"))
		fmt.Printf("%#v\n", nc.Int64("bonk"))
		fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
		fmt.Printf("%#v\n", nc.Int("bips"))
		fmt.Printf("%#v\n", nc.IntSlice("blups"))
		fmt.Printf("%#v\n", nc.String("snurt"))
		fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
		fmt.Printf("%#v\n", nc.Uint("flub"))
		fmt.Printf("%#v\n", nc.Uint64("florb"))
		fmt.Printf("%#v\n", nc.GlobalBool("global-nope"))
		fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp"))
		fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong"))
		fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay"))
		fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop"))
		fmt.Printf("%#v\n", nc.GlobalInt("global-bips"))
		fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups"))
		fmt.Printf("%#v\n", nc.GlobalString("global-snurt"))
		fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles"))

		fmt.Printf("%#v\n", nc.FlagNames())
		fmt.Printf("%#v\n", nc.GlobalFlagNames())
		fmt.Printf("%#v\n", nc.GlobalIsSet("wat"))
		fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope"))
		fmt.Printf("%#v\n", nc.NArg())
		fmt.Printf("%#v\n", nc.NumFlags())
		fmt.Printf("%#v\n", nc.Parent())

		nc.Set("wat", "also-nope")

		ec := cli.NewExitError("ohwell", 86)
		fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode())
		fmt.Printf("made it!\n")
		return ec
	}

	if os.Getenv("HEXY") != "" {
		app.Writer = &hexWriter{}
		app.ErrWriter = &hexWriter{}
	}

	app.Metadata = map[string]interface{}{
		"layers":          "many",
		"explicable":      false,
		"whatever-values": 19.99,
	}

	app.Run(os.Args)
}
コード例 #8
0
ファイル: add_private_key.go プロジェクト: skycoin/skycoin
func addPrivateKeyCMD() gcli.Command {
	name := "addPrivateKey"
	return gcli.Command{
		Name:      name,
		Usage:     "Add a private key to specific wallet",
		ArgsUsage: "[private key]",
		Description: fmt.Sprintf(`Add a private key to specific wallet, the default
		wallet(%s/%s) will be 
		used if the wallet file or path is not specified`,
			cfg.WalletDir, cfg.DefaultWalletName),
		Flags: []gcli.Flag{
			gcli.StringFlag{
				Name:  "f",
				Usage: "[wallet file or path] private key will be added to this wallet",
			},
		},
		OnUsageError: onCommandUsageError(name),
		Action: func(c *gcli.Context) error {
			// get private key
			skStr := c.Args().First()
			if skStr == "" {
				gcli.ShowSubcommandHelp(c)
				return nil
			}

			// get wallet file path
			w := c.String("f")
			if w == "" {
				w = filepath.Join(cfg.WalletDir, cfg.DefaultWalletName)
			}

			if !strings.HasSuffix(w, walletExt) {
				return errWalletName
			}

			// only wallet file name, no path.
			if filepath.Base(w) == w {
				w = filepath.Join(cfg.WalletDir, w)
			}

			wlt, err := wallet.Load(w)
			if err != nil {
				errorWithHelp(c, err)
				return nil
			}

			sk, err := cipher.SecKeyFromHex(skStr)
			if err != nil {
				return fmt.Errorf("invalid private key: %s, must be an hex string of length 64", skStr)
			}

			pk := cipher.PubKeyFromSecKey(sk)
			addr := cipher.AddressFromPubKey(pk)

			entry := wallet.WalletEntry{
				Address: addr,
				Public:  pk,
				Secret:  sk,
			}

			if err := wlt.AddEntry(entry); err != nil {
				return err
			}

			dir, err := filepath.Abs(filepath.Dir(w))
			if err != nil {
				return err
			}

			if err := wlt.Save(dir); err != nil {
				return errors.New("save wallet failed")
			}

			fmt.Println("success")

			return nil
		},
	}
	// Commands = append(Commands, cmd)
}
コード例 #9
0
ファイル: cli.go プロジェクト: eldarion-gondor/cli
// Run is @@@
func (c *CLI) Run() {
	app := cli.NewApp()
	app.Name = c.Name
	app.Version = c.Version
	app.Author = c.Author
	app.Email = c.Email
	app.Usage = c.Usage
	app.EnableBashCompletion = true
	app.Flags = []cli.Flag{}
	if c.Config.Cloud == nil {
		app.Flags = append(app.Flags, cli.StringFlag{
			Name:   "cloud",
			Value:  "",
			Usage:  "cloud used for this invocation",
			EnvVar: fmt.Sprintf("%s_CLOUD", c.EnvVarPrefix),
		})
	}
	app.Flags = append(app.Flags,
		cli.StringFlag{
			Name:   "cluster",
			Value:  "",
			Usage:  "cluster used for this invocation",
			EnvVar: fmt.Sprintf("%s_CLUSTER", c.EnvVarPrefix),
		},
		cli.StringFlag{
			Name:   "resource-group",
			Value:  "",
			Usage:  "resource group used for this invocation",
			EnvVar: fmt.Sprintf("%s_RESOURCE_GROUP", c.EnvVarPrefix),
		},
		cli.StringFlag{
			Name:   "site",
			Value:  "",
			Usage:  "site used for this invocation",
			EnvVar: fmt.Sprintf("%s_SITE", c.EnvVarPrefix),
		},
		cli.BoolFlag{
			Name:  "log-http",
			Usage: "log HTTP interactions",
		},
	)
	app.Action = func(ctx *cli.Context) error {
		c.checkVersion()
		cli.ShowAppHelp(ctx)
		return nil
	}
	app.Commands = []cli.Command{
		{
			Name:   "login",
			Usage:  fmt.Sprintf("authenticate with the %s", c.LongName),
			Action: c.cmd(loginCmd),
		},
		{
			Name:   "logout",
			Usage:  fmt.Sprintf("invalidate any existing credentials with the %s", c.LongName),
			Action: c.cmd(logoutCmd),
		},
		{
			Name:   "upgrade",
			Usage:  fmt.Sprintf("upgrade the client to latest version supported by the %s", c.LongName),
			Action: c.cmd(upgradeCmd),
		},
		{
			Name:  "resource-groups",
			Usage: "manage resource groups",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "show resource groups to which you belong",
					Action: c.cmd(c.stdCmd(resourceGroupListCmd)),
				},
			},
		},
		{
			Name:  "keypairs",
			Usage: "manage keypairs",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "List keypairs",
					Action: c.cmd(c.stdCmd(keypairsListCmd)),
				},
				{
					Name:  "create",
					Usage: "create a keypair",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "name",
							Value: "",
							Usage: "name of keypair",
						},
					},
					Action: c.cmd(c.stdCmd(keypairsCreateCmd)),
				},
				{
					Name:  "attach",
					Usage: "attach keypair to service",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
						cli.StringFlag{
							Name:  "keypair",
							Value: "",
							Usage: "name of keypair",
						},
						cli.StringFlag{
							Name:  "service",
							Value: "",
							Usage: "service path",
						},
					},
					Action: c.cmd(c.stdCmd(keypairsAttachCmd)),
				},
				{
					Name:  "detach",
					Usage: "detach keypair from service",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
						cli.StringFlag{
							Name:  "service",
							Value: "",
							Usage: "service name",
						},
					},
					Action: c.cmd(c.stdCmd(keypairsDetachCmd)),
					BashComplete: func(ctx *cli.Context) {
					},
				},
				{
					Name:   "delete",
					Usage:  "delete a keypair by name",
					Action: c.cmd(c.stdCmd(keypairsDeleteCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						resourceGroup := c.GetResourceGroup(ctx)
						keypairs, err := api.KeyPairs.List(&*resourceGroup.URL)
						if err != nil {
							return
						}
						for i := range keypairs {
							fmt.Println(*keypairs[i].Name)
						}
					},
				},
			},
		},
		{
			Name:  "sites",
			Usage: "manage sites",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "show sites in the resource group",
					Action: c.cmd(c.stdCmd(sitesListCmd)),
				},
				{
					Name:  "init",
					Usage: "create a site, production instance and write config",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "name",
							Value: "",
							Usage: "optional name for site",
						},
					},
					Action: c.cmd(c.stdCmd(sitesInitCmd)),
				},
				{
					Name:   "create",
					Usage:  "create a site in the resource group",
					Action: c.cmd(c.stdCmd(sitesCreateCmd)),
				},
				{
					Name:   "delete",
					Usage:  "delete a site in the resource group",
					Action: c.cmd(c.stdCmd(sitesDeleteCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						resourceGroup := c.GetResourceGroup(ctx)
						sites, err := api.Sites.List(&*resourceGroup.URL)
						if err != nil {
							return
						}
						for i := range sites {
							fmt.Println(*sites[i].Name)
						}
					},
				},
				{
					Name:   "env",
					Usage:  "",
					Action: c.cmd(c.stdCmd(sitesEnvCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						resourceGroup := c.GetResourceGroup(ctx)
						sites, err := api.Sites.List(&*resourceGroup.URL)
						if err != nil {
							return
						}
						for i := range sites {
							fmt.Println(*sites[i].Name)
						}
					},
				},
				{
					Name:  "users",
					Usage: "manage users",
					Action: c.cmd(func(c *CLI, ctx *cli.Context) {
						cli.ShowSubcommandHelp(ctx)
					}),
					Subcommands: []cli.Command{
						{
							Name:   "list",
							Usage:  "List users for the site",
							Action: c.cmd(c.stdCmd(sitesUsersListCmd)),
						},
						{
							Name:   "add",
							Usage:  "Add a user to site with a given role",
							Action: c.cmd(c.stdCmd(sitesUsersAddCmd)),
							Flags: []cli.Flag{
								cli.StringFlag{
									Name:  "role",
									Value: "dev",
									Usage: "desired role for user",
								},
							},
						},
					},
				},
			},
		},
		{
			Name:  "instances",
			Usage: "manage instances",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:  "create",
					Usage: "create new instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "kind",
							Value: "",
							Usage: "kind of instance",
						},
					},
					Action: c.cmd(c.stdCmd(instancesCreateCmd)),
				},
				{
					Name:   "list",
					Usage:  "",
					Action: c.cmd(c.stdCmd(instancesListCmd)),
				},
				{
					Name:   "delete",
					Usage:  "",
					Action: c.cmd(c.stdCmd(instancesDeleteCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						site := c.GetSite(ctx)
						instances, err := api.Instances.List(&*site.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range instances {
							fmt.Println(*instances[i].Label)
						}
					},
				},
				{
					Name:  "env",
					Usage: "",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(instancesEnvCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						site := c.GetSite(ctx)
						instances, err := api.Instances.List(&*site.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range instances {
							fmt.Println(*instances[i].Label)
						}
					},
				},
			},
		},
		{
			Name:  "services",
			Usage: "manage services",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:  "create",
					Usage: "create new service",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "name",
							Value: "",
							Usage: "name of the service",
						},
						cli.StringFlag{
							Name:  "version",
							Value: "",
							Usage: "version for the new service",
						},
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesCreateCmd)),
				},
				{
					Name:  "list",
					Usage: "",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesListCmd)),
				},
				{
					Name:  "config",
					Usage: "",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Subcommands: []cli.Command{
						{
							Name:   "get",
							Usage:  "",
							Action: c.cmd(c.stdCmd(servicesConfigGetCmd)),
						},
						{
							Name:   "set",
							Usage:  "",
							Action: c.cmd(c.stdCmd(servicesConfigSetCmd)),
						},
					},
				},
				{
					Name:  "delete",
					Usage: "",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesDeleteCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						instance := c.GetInstance(ctx, nil)
						services, err := api.Services.List(&*instance.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range services {
							fmt.Println(*services[i].Name)
						}
					},
				},
				{
					Name:  "env",
					Usage: "",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesEnvCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						instance := c.GetInstance(ctx, nil)
						services, err := api.Services.List(&*instance.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range services {
							fmt.Println(*services[i].Name)
						}
					},
				},
				{
					Name:  "scale",
					Usage: "scale up/down a service on an instance",
					Flags: []cli.Flag{
						cli.IntFlag{
							Name:  "replicas",
							Usage: "desired number of replicas",
						},
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesScaleCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						instance := c.GetInstance(ctx, nil)
						services, err := api.Services.List(&*instance.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range services {
							fmt.Println(*services[i].Name)
						}
					},
				},
				{
					Name:  "restart",
					Usage: "restart a service on a given instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(servicesRestartCmd)),
					BashComplete: func(ctx *cli.Context) {
						if len(ctx.Args()) > 0 {
							return
						}
						api := c.GetAPIClient(ctx)
						instance := c.GetInstance(ctx, nil)
						services, err := api.Services.List(&*instance.URL)
						if err != nil {
							fatal(err.Error())
						}
						for i := range services {
							fmt.Println(*services[i].Name)
						}
					},
				},
			},
		},
		{
			Name:  "run",
			Usage: "run a one-off process",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "instance",
					Value: "",
					Usage: "instance label",
				},
			},
			Action: c.cmd(c.stdCmd(runCmd)),
			BashComplete: func(ctx *cli.Context) {
				if len(ctx.Args()) > 0 {
					return
				}
				api := c.GetAPIClient(ctx)
				instance := c.GetInstance(ctx, nil)
				services, err := api.Services.List(&*instance.URL)
				if err != nil {
					fatal(err.Error())
				}
				for i := range services {
					fmt.Println(*services[i].Name)
				}
			},
		},
		{
			Name:  "deploy",
			Usage: "create a new release and deploy",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "instance",
					Value: "",
					Usage: "instance label",
				},
			},
			Action: c.cmd(c.stdCmd(deployCmd)),
		},
		{
			Name:  "hosts",
			Usage: "manage hosts for an instance",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:  "list",
					Usage: "List hosts for an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(hostsListCmd)),
				},
				{
					Name:  "create",
					Usage: "Create a host for an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(hostsCreateCmd)),
				},
				{
					Name:  "delete",
					Usage: "Delete a host from an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(hostsDeleteCmd)),
				},
			},
		},
		{
			Name:  "scheduled-tasks",
			Usage: "manage scheduled tasks for an instance",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				cli.ShowSubcommandHelp(ctx)
			}),
			Subcommands: []cli.Command{
				{
					Name:  "list",
					Usage: "List scheduled tasks for an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(scheduledTasksListCmd)),
				},
				{
					Name:  "create",
					Usage: "Create a scheduled task for an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
						cli.StringFlag{
							Name:  "name",
							Value: "",
							Usage: "scheduled task name",
						},
						cli.StringFlag{
							Name:  "timezone",
							Value: "UTC",
							Usage: "scheduled task timezone (default: UTC)",
						},
						cli.StringFlag{
							Name:  "schedule",
							Value: "",
							Usage: "scheduled task schedule (cron syntax)",
						},
					},
					Action: c.cmd(c.stdCmd(scheduledTasksCreateCmd)),
				},
				{
					Name:  "delete",
					Usage: "Delete a scheduled task from an instance",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "instance",
							Value: "",
							Usage: "instance label",
						},
					},
					Action: c.cmd(c.stdCmd(scheduledTasksDeleteCmd)),
				},
			},
		},
		{
			Name:   "open",
			Usage:  "open instance URL in browser",
			Action: c.cmd(c.stdCmd(openCmd)),
			BashComplete: func(ctx *cli.Context) {
				if len(ctx.Args()) > 0 {
					return
				}
				api := c.GetAPIClient(ctx)
				instance := c.GetInstance(ctx, nil)
				services, err := api.Services.List(&*instance.URL)
				if err != nil {
					fatal(err.Error())
				}
				for i := range services {
					if *services[i].Kind == "web" {
						fmt.Println(*services[i].Name)
					}
				}
			},
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "instance",
					Value: "",
					Usage: "instance label",
				},
			},
		},
		{
			Name:  "logs",
			Usage: "view logs for an instance or service",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "instance",
					Value: "",
					Usage: "instance label",
				},
				cli.IntFlag{
					Name:  "lines",
					Value: 20,
					Usage: "number of lines to query",
				},
			},
			Action: c.cmd(c.stdCmd(logsCmd)),
			BashComplete: func(ctx *cli.Context) {
				if len(ctx.Args()) > 0 {
					return
				}
				api := c.GetAPIClient(ctx)
				instance := c.GetInstance(ctx, nil)
				services, err := api.Services.List(&*instance.URL)
				if err != nil {
					fatal(err.Error())
				}
				for i := range services {
					fmt.Println(*services[i].Name)
				}
			},
		},
		{
			Name:  "metrics",
			Usage: "view metrics for a given service",
			Action: c.cmd(func(c *CLI, ctx *cli.Context) {
				api := c.GetAPIClient(ctx)
				site := c.GetSite(ctx)
				if len(ctx.Args()) != 1 {
					fatal("missing service")
				}
				parts := strings.Split(ctx.Args()[0], "/")
				instanceLabel := parts[0]
				serviceName := parts[1]
				instance, err := api.Instances.Get(*site.URL, instanceLabel)
				if err != nil {
					fatal(err.Error())
				}
				service, err := api.Services.Get(*instance.URL, serviceName)
				if err != nil {
					fatal(err.Error())
				}
				series, err := api.Metrics.List(*service.URL)
				if err != nil {
					fatal(err.Error())
				}
				for i := range series {
					s := series[i]
					fmt.Printf("%s = ", s.Name)
					for j := range s.Points {
						value := s.Points[j][2]
						switch *s.Name {
						case "filesystem/limit_bytes_gauge", "filesystem/usage_bytes_gauge", "memory/usage_bytes_gauge", "memory/working_set_bytes_gauge":
							fmt.Printf("%s ", bytefmt.ByteSize(uint64(value)))
							break
						default:
							fmt.Printf("%d ", value)
						}
					}
					fmt.Println("")
				}
			}),
		},
	}
	app.Run(os.Args)
}