carlos / platform Public

cli: every aliased flag registers through the helpers; every verb prints through flagUsage

Mechanical sweep, no behaviour change at the parser: 60 -app, 9
-channel (one keeping its stable default via aliasedString), 8
-environment and 2 -version registrations convert to the helpers; all
84 command flag sets wire fs.Usage = flagUsage(fs, <their synopsis
const, or "" for the derived one>); the three hand fs.Usage overrides
(logs, economics backhaul, pipeline) convert, logsUsage dropping its
hand-kept flag list — the drift surface the shared printer exists to
close. The C46 deliver-line matchers in services_cli_test.go read the
new row shape, still name-exact and still rejecting fall-through pages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 4264f98e000ea82d7a46d04bda77f29d6ceed0ab parent 597c2de
21 files changed, +185 −113
  • accounts.go +4 −1
  • appconfig_cli.go +25 −16
  • apps.go +7 −3
  • auth.go +4 −0
  • cli.go +36 −18
  • deploy.go +4 −3
  • domains_cli.go +6 −3
  • economics.go +2 −1
  • fleets.go +7 −1
  • instances_cli.go +17 −10
  • logs.go +2 −20
  • mail_cli.go +18 −9
  • pipeline.go +5 −6
  • restart.go +2 −1
  • retention.go +2 −1
  • routes_cli.go +2 −1
  • schedule.go +8 −4
  • services.go +5 −1
  • services_cli_test.go +20 −10
  • store.go +8 −4
  • update.go +1 −0
diff --git a/accounts.go b/accounts.go
index d20bcac4f..8aef6de91 100644
--- a/accounts.go
+++ b/accounts.go
@@ -89,6 +89,7 @@ func runAccountsCreate(args []string) {
owner := fs.String("owner", "", "keymail address that owns the account — the box-side/headless path, "+
"and what to use when this terminal is not logged in")
console := fs.String("console", "", "which logged-in console to create it through (only needed with several)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
*name = strings.TrimSpace(*name)
if *name == "" {
@@ -212,6 +213,7 @@ func runAccountsList(args []string) {
fs := flag.NewFlagSet("accounts list", flag.ExitOnError)
regPath := fs.String("registry", defaultRegistry(), "registry database (box mode)")
consoleFlag := fs.String("console", "", "list through a console instead (default: the box registry when one exists, else your logged-in console)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
// Mode split by what exists, not by resolveMode: this verb predates the
// console API and must keep working on a box that has a registry and no
@@ -310,10 +312,11 @@ func runAccountsListRegistry(regPath string) {
func runAccountsMigrate(args []string) {
fs := flag.NewFlagSet("accounts migrate", flag.ExitOnError)
regPath := fs.String("registry", defaultRegistry(), "registry database")
- app := fs.String("app", "", "app to move")
+ app := appFlag(fs, "app to move")
to := fs.String("to", "", "destination account sqid")
from := fs.String("from", "", "source account sqid, or \"legacy\" for the pre-accounts keys (default: ops, then legacy)")
dryRun := fs.Bool("dry-run", false, "print the plan and touch nothing")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if *app == "" || *to == "" {
log.Fatal("usage: carlos accounts migrate -app <a> -to <sqid> [-from <sqid>] [-dry-run]")
diff --git a/appconfig_cli.go b/appconfig_cli.go
index 9ad738c13..659c9d335 100644
--- a/appconfig_cli.go
+++ b/appconfig_cli.go
@@ -201,10 +201,11 @@ func parseKV(args []string) (map[string]string, error) {
func runEnvSet(args []string) {
fs := flag.NewFlagSet("env set", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to set in (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to set in (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env set -app <a> [-environment <name>] [-console <origin>] KEY=value [KEY2=value2 ...]"
checkEnvironmentFlag(*environment)
@@ -244,10 +245,11 @@ func runEnvSet(args []string) {
func runEnvUnset(args []string) {
fs := flag.NewFlagSet("env unset", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to remove from (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to remove from (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env unset -app <a> [-environment <name>] [-console <origin>] KEY [KEY2 ...]"
if fs.NArg() == 0 {
@@ -284,10 +286,11 @@ func runEnvUnset(args []string) {
func runEnvList(args []string) {
fs := flag.NewFlagSet("env list", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to list (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to list (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env list -app <a> [-environment <name>] [-console <origin>]"
checkEnvironmentFlag(*environment)
@@ -386,11 +389,12 @@ func printEnvList(app, environment, execDelivery string, vars map[string]string)
// is refused there, by name, rather than accepted as a synonym.
func runEnvExecDelivery(args []string) {
fs := flag.NewFlagSet("env exec-delivery", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "",
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs,
"named environment bundle to set the flag in (omit for the app's default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env exec-delivery -app <a> [-console <origin>] file|off\n" +
" or: carlos env exec-delivery -app <a> -environment <name> [-console <origin>] file|inherit"
@@ -474,9 +478,10 @@ func bundleAppName(app string) string {
func runEnvEnvironments(args []string) {
fs := flag.NewFlagSet("env environments", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env environments -app <a> [-console <origin>]"
@@ -527,9 +532,10 @@ func runEnvEnvironments(args []string) {
// per-bundle" design (generation.go).
func runEnvSync(args []string) {
fs := flag.NewFlagSet("env sync", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos env sync -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -648,10 +654,11 @@ func secretsPubKeyAPI(sess *apiSession, account apiAcct, app string) *ecdh.Publi
func runSecretsSet(args []string) {
fs := flag.NewFlagSet("secrets set", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to set in (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to set in (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos secrets set -app <a> [-environment <name>] [-console <origin>] KEY=value [KEY2=value2 ...]"
checkEnvironmentFlag(*environment)
@@ -720,10 +727,11 @@ func runSecretsSet(args []string) {
func runSecretsUnset(args []string) {
fs := flag.NewFlagSet("secrets unset", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to remove from (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to remove from (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos secrets unset -app <a> [-environment <name>] [-console <origin>] KEY [KEY2 ...]"
if fs.NArg() == 0 {
@@ -772,10 +780,11 @@ func printSecretsList(app, environment string, keys []string) {
func runSecretsList(args []string) {
fs := flag.NewFlagSet("secrets list", flag.ExitOnError)
- app := fs.String("app", "", "app name")
- environment := fs.String("environment", "", "named environment bundle to list (omit for the default bundle)")
+ app := appFlag(fs, "")
+ environment := environmentFlag(fs, "named environment bundle to list (omit for the default bundle)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to work through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos secrets list -app <a> [-environment <name>] [-console <origin>]"
checkEnvironmentFlag(*environment)
diff --git a/apps.go b/apps.go
index baee56d07..9f6c19bfb 100644
--- a/apps.go
+++ b/apps.go
@@ -180,6 +180,7 @@ func runAppsDelete(args []string) {
fs := flag.NewFlagSet("apps delete", flag.ExitOnError)
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to delete through (signed-in mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos apps delete [-account <sqid|name>] [-account-id <sqid>] [-console <origin>] <app>")
@@ -300,6 +301,7 @@ func runAppsRestore(args []string) {
fs := flag.NewFlagSet("apps restore", flag.ExitOnError)
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to restore through (signed-in mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos apps restore [-account <sqid|name>] [-account-id <sqid>] [-console <origin>] <app>")
@@ -378,7 +380,7 @@ func validPlaceTarget(target string) error {
func runAppsCreate(args []string) {
fs := flag.NewFlagSet("apps create", flag.ExitOnError)
- app := fs.String("app", "", "app name to claim (lowercase letters, digits, dashes)")
+ app := appFlag(fs, "app name to claim (lowercase letters, digits, dashes)")
place := fs.String("place", "", `place the app on a customer fleet at create time — "" (default) or "fleet/<name>"`)
// Not accountFlags: that helper also registers -registry for direct-mode
// resolution, and this verb never resolves locally — the console resolves
@@ -386,6 +388,7 @@ func runAppsCreate(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) to claim the app in (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account; this is the flag an ambiguity error tells you to use")
consoleFlag := fs.String("console", "", "console to create it through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if strings.TrimSpace(*app) == "" {
log.Fatal("usage: carlos apps create -app <name> [-place fleet/<f>] [-account <sqid|name>] " +
@@ -471,7 +474,7 @@ func placementMismatchErr(app, requested, echoed string) error {
// local refusal, not a spent write-rate-limit slot.
func runAppsPlace(args []string) {
fs := flag.NewFlagSet("apps place", flag.ExitOnError)
- app := fs.String("app", "", "app to place")
+ app := appFlag(fs, "app to place")
target := fs.String("target", "", `fleet target — "" clears placement, or "fleet/<name>"`)
// Not accountFlags: that helper also registers -registry for direct-mode
// resolution, and this verb never resolves locally — placement is
@@ -479,9 +482,10 @@ func runAppsPlace(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account; this is the flag an ambiguity error tells you to use")
consoleFlag := fs.String("console", "", "console to place through (default: the only logged-in one)")
- fs.Parse(args)
const placeUsage = `usage: carlos apps place -app <name> -target fleet/<f>|"" [-account <sqid|name>] ` +
"[-account-id <sqid>] [-console <origin>]"
+ fs.Usage = flagUsage(fs, placeUsage)
+ fs.Parse(args)
if strings.TrimSpace(*app) == "" {
log.Fatal(placeUsage)
}
diff --git a/auth.go b/auth.go
index 81c66d55f..d34241836 100644
--- a/auth.go
+++ b/auth.go
@@ -629,6 +629,7 @@ func authLogin(out io.Writer, args []string) error {
fs.SetOutput(out)
console := fs.String("console", "", "the console to log in to (default: the configured default, else "+defaultConsole+")")
label := fs.String("label", "", "what to call this terminal on the console (default: user@host)")
+ fs.Usage = flagUsage(fs, "")
if err := fs.Parse(args); err != nil {
return err
}
@@ -736,6 +737,7 @@ func authWhoami(out io.Writer, args []string) error {
fs := flag.NewFlagSet("auth whoami", flag.ContinueOnError)
fs.SetOutput(out)
console := fs.String("console", "", "which console (only needed with several)")
+ fs.Usage = flagUsage(fs, "")
if err := fs.Parse(args); err != nil {
return err
}
@@ -790,6 +792,7 @@ func authLogout(out io.Writer, args []string) error {
fs := flag.NewFlagSet("auth logout", flag.ContinueOnError)
fs.SetOutput(out)
console := fs.String("console", "", "which console (only needed with several)")
+ fs.Usage = flagUsage(fs, "")
if err := fs.Parse(args); err != nil {
return err
}
@@ -835,6 +838,7 @@ func authDefault(out io.Writer, args []string) error {
fs := flag.NewFlagSet("auth default", flag.ContinueOnError)
fs.SetOutput(out)
project := fs.Bool("project", false, "write ./.carlos/config (this directory) instead of ~/.carlos/config")
+ fs.Usage = flagUsage(fs, "")
if err := fs.Parse(args); err != nil {
return err
}
diff --git a/cli.go b/cli.go
index bc352d0b0..ae7e31af7 100644
--- a/cli.go
+++ b/cli.go
@@ -68,19 +68,20 @@ func reservedSlugs() map[string]bool {
func runAdd(args []string) {
fs := flag.NewFlagSet("add", flag.ExitOnError)
reg := fs.String("registry", defaultRegistry(), "registry database")
- app := fs.String("app", "", "app whose releases this route runs")
+ app := appFlag(fs, "app whose releases this route runs")
acct := fs.String("account", "ops", "account (sqid or name) the app belongs to")
acctID := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
slug := fs.String("slug", "", "root-domain short name (optional)")
kind := fs.String("kind", "instance", "instance | service | static")
socket := fs.String("socket", "", "unix socket upstream")
addr := fs.String("addr", "", "TCP upstream host:port (whole-app tenants)")
- channel := fs.String("channel", "stable", "release channel to follow")
+ channel := aliasedString(fs, "channel", "stable", "release channel to follow")
hibernate := fs.Bool("hibernate", false, "hibernate when idle")
statusNamed := fs.Bool("status-named", false, "opt this app into being named on the public status page")
backing := fs.String("backing", "", `process backing: "" (agent exec child) | "unit" (carlos-app@<host> systemd unit)`)
unit := fs.String("unit", "", "systemd unit that owns this route's process when it is neither an exec child nor a carlos-app@ tenant (e.g. console.service)")
dbPath := fs.String("db", "", "instance database path")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos add [flags] <host>")
@@ -249,6 +250,7 @@ func runAdd(args []string) {
func runRemove(args []string) {
fs := flag.NewFlagSet("remove", flag.ExitOnError)
reg := fs.String("registry", defaultRegistry(), "registry database")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos remove <host>")
@@ -294,6 +296,7 @@ func runInstances(args []string) {
}
fs := flag.NewFlagSet("instances", flag.ExitOnError)
reg := fs.String("registry", defaultRegistry(), "registry database")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
r, err := registry.Open(*reg)
if err != nil {
@@ -455,11 +458,11 @@ func runRoute(args []string) {
fs := flag.NewFlagSet("route", flag.ExitOnError)
regPath := fs.String("registry", defaultRegistry(), "registry database")
host := fs.String("host", "", "the one route to repoint")
- app := fs.String("app", "", "repoint every non-platform route of this app")
+ app := appFlag(fs, "repoint every non-platform route of this app")
acctFilter := fs.String("account", "ops", `account (sqid or name) to filter -app routes by; "all" for every account`)
acctIDFilter := fs.String("account-id", "", "account sqid to filter -app routes by, matched as a sqid ONLY — supersedes -account")
- channel := fs.String("channel", "", "release channel to subscribe the route(s) to")
- environment := fs.String("environment", "",
+ channel := channelFlag(fs, "release channel to subscribe the route(s) to")
+ environment := environmentFlag(fs,
`named config environment to bind the route(s) to (must already exist in the bucket); "-" clears to default`)
grant := fs.String("grant", "", "capability token to grant this route ON THIS BOX: port:<n> (privileged, never 22/80/443) | host-role")
revoke := fs.String("revoke", "", "capability token to revoke from this route on this box")
@@ -473,6 +476,7 @@ func runRoute(args []string) {
"allow repointing to a channel that RESOLVES but holds a different version than the route currently serves "+
"(never overrides an unresolvable target, a bad signature, or a missing pubkey)")
dryRun := fs.Bool("dry-run", false, "run the full safety-rail check and print the plan, but write nothing")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
// -hibernate has to be TRI-state: a plain flag.Bool cannot tell "the
// operator did not mention hibernation" from "the operator asked for
@@ -1717,16 +1721,17 @@ func publicKey() ed25519.PublicKey {
func runShip(args []string) {
fs := flag.NewFlagSet("ship", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to ship through (API mode only; default: the only logged-in one)")
kind := fs.String("kind", "binary", "binary | static")
- ver := fs.String("version", "", "version (git short sha); omit to mint the next iteration under the app's version target")
+ ver := versionFlag(fs, "version (git short sha); omit to mint the next iteration under the app's version target")
target := fs.String("target", "linux-arm64", "GOOS-GOARCH the binary was built for (binary kind, single file)")
capsFile := fs.String("capabilities", "", "path to a JSON capability block (internal/caps) to embed in the manifest")
label := fs.String("label", "", "one-line human label for this release (\"fixing modals\")")
notes := fs.String("notes", "", "changelog fragment for this release")
notesFile := fs.String("notes-file", "", "file holding the changelog fragment (mutually exclusive with -notes)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
// -version is no longer required: with a version target set, the next
// iteration under it is inferable, and inferring it is the whole point of
@@ -2050,10 +2055,11 @@ func runShipAPI(sess *apiSession, acct apiAcct, fs *flag.FlagSet, app, ver, kind
// bypass — explicit, so it can be seen.
func runPromote(args []string) {
fs := flag.NewFlagSet("promote", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
hotfix := fs.Bool("hotfix", false, "bypass the channel ladder (recorded as a hotfix)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to promote through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos promote -app <a> [-hotfix] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>] <version> <channel>"
if fs.NArg() != 2 {
@@ -2143,9 +2149,10 @@ func promoteActor() string {
func runChannels(args []string) {
fs := flag.NewFlagSet("channels", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos channels -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -2235,9 +2242,10 @@ func runChannels(args []string) {
// program, typed by every install script there is.
func runVersionTarget(args []string) {
fs := flag.NewFlagSet("version target", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read or set through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos version target -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>] [<semver>]"
if fs.NArg() > 1 {
@@ -2396,9 +2404,10 @@ func localReleases(ctx context.Context, s release.Store, acct, app string) ([]re
// there to run".
func runReleases(args []string) {
fs := flag.NewFlagSet("releases", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos releases -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -2447,9 +2456,10 @@ func runReleases(args []string) {
func runRollback(args []string) {
fs := flag.NewFlagSet("rollback", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to roll back through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, ledgerUsage)
fs.Parse(args)
usage := "usage: carlos rollback -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>] <channel>"
if fs.NArg() != 1 {
@@ -2597,9 +2607,10 @@ func ledgerAccountFlags(fs *flag.FlagSet) (raw, rawID *string) {
func runLedgerAppend(args []string) {
fs := flag.NewFlagSet("ledger append", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw := ledgerAccountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to append through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos ledger append -app <a> [-account <acct>] [-account-id <sqid>] <ledger> <entry.json|->"
if fs.NArg() != 2 {
@@ -2644,9 +2655,10 @@ func runLedgerAppend(args []string) {
// a JSON blob.
func runLedgerHead(args []string) {
fs := flag.NewFlagSet("ledger head", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw := ledgerAccountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos ledger head -app <a> [-account <acct>] [-account-id <sqid>] <ledger>"
if fs.NArg() != 1 {
@@ -2673,9 +2685,10 @@ func runLedgerHead(args []string) {
func runLedgerList(args []string) {
fs := flag.NewFlagSet("ledger list", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw := ledgerAccountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos ledger list -app <a> [-account <acct>] [-account-id <sqid>]"
@@ -2721,11 +2734,12 @@ func runLedgerList(args []string) {
func runLedgerPublish(args []string) {
fs := flag.NewFlagSet("ledger publish", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw := ledgerAccountFlags(fs)
public := fs.Bool("public", true, "serve these ledgers publicly (-public=false unpublishes)")
title := fs.String("title", "", "display title for the published set")
consoleFlag := fs.String("console", "", "console origin to publish through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos ledger publish -app <a> [-account <acct>] [-account-id <sqid>] [-public=true|false] [-title <t>] <ledger>..."
if fs.NArg() == 0 {
@@ -2766,9 +2780,10 @@ func runLedgerPublish(args []string) {
func runLedgerBlob(args []string) {
fs := flag.NewFlagSet("ledger blob", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw := ledgerAccountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to upload through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos ledger blob -app <a> [-account <acct>] [-account-id <sqid>] <ledger> <file>..."
if fs.NArg() < 2 {
@@ -2810,9 +2825,10 @@ func runLedgerBlob(args []string) {
// here).
func runLedgerVerify(args []string) {
fs := flag.NewFlagSet("ledger verify", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
account := fs.String("account", "", "account SQID (not name) — the public status-host path is keyed by sqid")
urlBase := fs.String("url", "", "public status-host base URL, e.g. https://status.example.com — no credentials needed")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if *app == "" || *account == "" || *urlBase == "" || fs.NArg() != 1 {
log.Fatal("usage: carlos ledger verify -app <a> -account <sqid> -url <https://status-host> <ledger>")
@@ -2855,6 +2871,7 @@ func runKeygen(args []string) {
scope := fs.String("scope", "", "mint a SCOPED key for these rungs, comma separated (canary,edge,beta) — stable is never delegable")
name := fs.String("name", "", "label for the scoped key; it appears in every refusal it causes")
expires := fs.Duration("expires", defaultGrantTTL, "how long the scoped key may sign for (max 8760h)")
+ fs.Usage = flagUsage(fs, statusUsage)
fs.Parse(args)
if *scope == "" {
@@ -2938,6 +2955,7 @@ func RunStatus(args []string) {
fs := flag.NewFlagSet("status", flag.ExitOnError)
dryRunDir := fs.String("dry-run", "", "build a fixture fleet, publish it into this directory, and verify no leaks")
liveFlag := fs.Bool("live", false, "read status/boxes/*.json from the status bucket and print per-host metrics")
+ fs.Usage = flagUsage(fs, statusUsage)
fs.Parse(args)
if *liveFlag {
diff --git a/deploy.go b/deploy.go
index 341fc2d40..b9d3dbea4 100644
--- a/deploy.go
+++ b/deploy.go
@@ -34,16 +34,17 @@ import (
// never a box command, per the operate-via-API rule.
func runDeploy(args []string) {
fs := flag.NewFlagSet("deploy", flag.ExitOnError)
- app := fs.String("app", "", "app name")
+ app := appFlag(fs, "")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to deploy through (API mode only; default: the only logged-in one)")
kind := fs.String("kind", "binary", "binary | static")
- ver := fs.String("version", "", "version (default: git rev-parse --short HEAD)")
+ ver := versionFlag(fs, "version (default: git rev-parse --short HEAD)")
target := fs.String("target", "linux-arm64", "GOOS-GOARCH the binary was built for (binary kind, single file)")
capsFile := fs.String("capabilities", "", "path to a JSON capability block (internal/caps) to embed in the manifest")
- channelFlag := fs.String("channel", "", "channel to promote to (default: resolved from the app's instances, or its one channel if it has no instances; error if ambiguous)")
+ channelFlag := channelFlag(fs, "channel to promote to (default: resolved from the app's instances, or its one channel if it has no instances; error if ambiguous)")
hostFlag := fs.String("host", "", "serve on this hostname instead of the app's own platform host (also scopes channel resolution and the watch to it)")
timeout := fs.Duration("timeout", defaultDeployTimeout, "how long to watch the URL before failing")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos deploy -app <a> [-version <sha>] [-kind binary|static] [-target linux-arm64] " +
"[-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>] [-capabilities <file.json>] " +
diff --git a/domains_cli.go b/domains_cli.go
index 38514d743..8439f201b 100644
--- a/domains_cli.go
+++ b/domains_cli.go
@@ -71,13 +71,14 @@ func runDomains(args []string) {
func runDomainsAttach(args []string) {
fs := flag.NewFlagSet("domains attach", flag.ExitOnError)
- app := fs.String("app", "", "app the hostname belongs to")
+ app := appFlag(fs, "app the hostname belongs to")
route := fs.String("route", "", "route hostname to attach to")
wildcard := fs.Bool("wildcard", false, "enable a delegated wildcard cert for this hostname (needs a CNAME — see the 422 message if refused)")
catchall := fs.Bool("catchall", false, "derive a *.<host> catch-all alias to the same route (needs -wildcard)")
consoleFlag := fs.String("console", "", "console to attach through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, domainsAttachUsage)
fs.Parse(args)
host := strings.TrimSpace(fs.Arg(0))
if host == "" || fs.NArg() > 1 {
@@ -155,10 +156,11 @@ func printDomainDNSLine(ctx context.Context, sess *apiSession, acct apiAcct, app
func runDomainsDetach(args []string) {
fs := flag.NewFlagSet("domains detach", flag.ExitOnError)
- app := fs.String("app", "", "app the hostname belongs to")
+ app := appFlag(fs, "app the hostname belongs to")
consoleFlag := fs.String("console", "", "console to detach through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, domainsDetachUsage)
fs.Parse(args)
host := strings.TrimSpace(fs.Arg(0))
if host == "" || fs.NArg() > 1 {
@@ -181,10 +183,11 @@ func runDomainsDetach(args []string) {
func runDomainsList(args []string) {
fs := flag.NewFlagSet("domains list", flag.ExitOnError)
- app := fs.String("app", "", "app whose hostname claims to list")
+ app := appFlag(fs, "app whose hostname claims to list")
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, domainsListUsage)
fs.Parse(args)
sess := requireAPISession("list", *consoleFlag)
printUsing(stdout, sess)
diff --git a/economics.go b/economics.go
index 9c99ecb61..10cdf0edd 100644
--- a/economics.go
+++ b/economics.go
@@ -201,7 +201,7 @@ func runEconomicsBackhaul(args []string) {
fs := flag.NewFlagSet("economics backhaul", flag.ExitOnError)
period := fs.String("period", "", "mtd (default), last, or 7d")
consoleFlag := fs.String("console", "", "console to ask (default: the only logged-in one)")
- fs.Usage = func() { fmt.Fprint(os.Stderr, economicsBackhaulUsage) }
+ fs.Usage = flagUsage(fs, economicsBackhaulUsage)
fs.Parse(args)
sess, err := resolveMode(*consoleFlag)
if err != nil {
@@ -289,6 +289,7 @@ func runEconomicsBill(args []string) {
lines := serviceLines{}
fs.Var(lines, "line", "one line item, repeatable: Service=usd")
consoleFlag := fs.String("console", "", "console to record through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, economicsUsage)
fs.Parse(args)
set := map[string]bool{}
diff --git a/fleets.go b/fleets.go
index efaa88cda..d70d05b74 100644
--- a/fleets.go
+++ b/fleets.go
@@ -159,6 +159,7 @@ func runFleetsCreate(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) to own the fleet (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to create it through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 || strings.TrimSpace(*bucket) == "" {
log.Fatal("usage: carlos fleets create -bucket <bucket> [-account <sqid|name>] " +
@@ -186,6 +187,7 @@ func runFleetsAddBox(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the fleet belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to add the box through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 2 {
log.Fatal("usage: carlos fleets add-box [-account <sqid|name>] [-account-id <sqid>] " +
@@ -214,6 +216,7 @@ func runFleetsRotateToken(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the fleet belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to rotate through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 2 {
log.Fatal("usage: carlos fleets rotate-token [-account <sqid|name>] [-account-id <sqid>] " +
@@ -278,9 +281,10 @@ func runFleetsTagBox(args []string) {
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to tag through (default: the only logged-in one)")
tags := fs.String("tags", "", `comma-separated tags, full replace ("" clears every tag) — required`)
- fs.Parse(args)
const tagBoxUsage = "usage: carlos fleets tag-box -tags <tags> [-account <sqid|name>] [-account-id <sqid>] " +
"[-console <origin>] <fleet> <label>"
+ fs.Usage = flagUsage(fs, tagBoxUsage)
+ fs.Parse(args)
if fs.NArg() != 2 {
log.Fatal(tagBoxUsage)
}
@@ -323,6 +327,7 @@ func runFleetsDetachBox(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the fleet belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to detach through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 2 {
log.Fatal("usage: carlos fleets detach-box [-account <sqid|name>] [-account-id <sqid>] " +
@@ -350,6 +355,7 @@ func runFleetsList(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) to list fleets for (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
sess := fleetsSignedInSession(*consoleFlag)
diff --git a/instances_cli.go b/instances_cli.go
index f4859da26..8d62d67dd 100644
--- a/instances_cli.go
+++ b/instances_cli.go
@@ -74,18 +74,19 @@ until this is set (spec §3.1).
func runInstancesEnable(args []string) {
fs := flag.NewFlagSet("instances enable", flag.ExitOnError)
- app := fs.String("app", "", "app whose instances this enables")
+ app := appFlag(fs, "app whose instances this enables")
domain := fs.String("domain", "", "instance host domain suffix (store mode only)")
pool := fs.String("pool", "", "pool stamped into every record at create (store mode only)")
// Default "" so signed-in mode can tell "unset" from "asked for": an
// omitted -channel must preserve whatever the manifest already carries,
// not silently reset it to stable. Store mode applies the documented
// "stable" default below, so its behaviour is unchanged.
- channel := fs.String("channel", "", "release channel stamped into every record at create")
+ channel := channelFlag(fs, "release channel stamped into every record at create")
regions := fs.String("regions", "", "comma-separated region allow-list (store mode only); empty = any shape-valid region")
seed := fs.String("seed", "", "argv of the app's first-boot init command, whitespace-split (strings.Fields)")
consoleFlag := fs.String("console", "", "console to enable through (signed-in mode only; default: the only logged-in one)")
acctRaw, acctIDRaw, acctOf := accountFlags(fs)
+ fs.Usage = flagUsage(fs, instancesEnableUsage)
fs.Parse(args)
if *app != "" && !release.ValidApp(*app) {
log.Fatalf("carlos instances enable: %q is not a valid app name", *app)
@@ -292,10 +293,10 @@ func requireAPISession(verb, consoleFlag string) *apiSession {
func runInstancesCreate(args []string) {
fs := flag.NewFlagSet("instances create", flag.ExitOnError)
- app := fs.String("app", "", "app the instance belongs to")
+ app := appFlag(fs, "app the instance belongs to")
host := fs.String("host", "", "instance host — one DNS label + \".\" + the app's instance domain")
region := fs.String("region", "", "region code for the record (subject to the manifest's allow-list)")
- channel := fs.String("channel", "", "release channel the instance follows (default: the manifest's, then stable)")
+ channel := channelFlag(fs, "release channel the instance follows (default: the manifest's, then stable)")
kind := fs.String("kind", "", "route class: instance (default) | service | static — a static record serves a file tree off the channel pointer and has no process")
addr := fs.String("addr", "", "comma-separated TCP upstream set, host:port each (e.g. 10.0.0.1:8090,10.0.0.2:8090); omitted means the platform's own default upstream")
healthPath := fs.String("health-path", "", "states the whole probe alongside -health-status: request path (default \"/\"); honoured for a single -addr too")
@@ -303,6 +304,7 @@ func runInstancesCreate(args []string) {
consoleFlag := fs.String("console", "", "console to create through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, instancesRecordsUsage)
fs.Parse(args)
if *host == "" {
log.Fatal(instancesRecordsUsage)
@@ -377,12 +379,13 @@ func upstreamsCell(ups []apiUpstreamState) string {
func runInstancesList(args []string) {
fs := flag.NewFlagSet("instances list", flag.ExitOnError)
- app := fs.String("app", "", "app whose instance records to list")
+ app := appFlag(fs, "app whose instance records to list")
match := fs.String("match", "", "only hosts containing this substring")
health := fs.String("health", "", "only this state: running, serving, setting-up, asleep, no-readings, not-responding, failed")
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, instancesRecordsUsage)
fs.Parse(args)
sess := requireAPISession("list", *consoleFlag)
acct := resolvedAccount(sess, acctRaw, acctIDRaw)
@@ -417,11 +420,12 @@ func runInstancesList(args []string) {
func runInstancesDelete(args []string) {
fs := flag.NewFlagSet("instances delete", flag.ExitOnError)
- app := fs.String("app", "", "app the instance belongs to")
+ app := appFlag(fs, "app the instance belongs to")
host := fs.String("host", "", "instance host whose record to retire")
consoleFlag := fs.String("console", "", "console to delete through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, instancesRecordsUsage)
fs.Parse(args)
if *host == "" {
log.Fatal(instancesRecordsUsage)
@@ -454,7 +458,7 @@ func runInstancesDelete(args []string) {
// operator guidance, not something to summarize).
func runInstancesSetUpstreams(args []string) {
fs := flag.NewFlagSet("instances set-upstreams", flag.ExitOnError)
- app := fs.String("app", "", "app the instance belongs to")
+ app := appFlag(fs, "app the instance belongs to")
host := fs.String("host", "", "instance host whose upstream set to repoint")
addrsFlag := fs.String("addrs", "", "comma-separated TCP upstream set, host:port each, replacing the record's whole upstream set; omitted leaves the existing set alone; an explicit -addrs \"\" clears it (400 for a kind:instance record — the DerivesPaths rail refuses that shape transition in place)")
healthPath := fs.String("health-path", "", "states the WHOLE probe block, replacing whatever was there — not a per-field update; a field neither flag names reverts to the platform's own default")
@@ -462,6 +466,7 @@ func runInstancesSetUpstreams(args []string) {
consoleFlag := fs.String("console", "", "console to update through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, instancesRecordsUsage)
fs.Parse(args)
if *host == "" {
log.Fatal(instancesRecordsUsage)
@@ -565,11 +570,12 @@ opt-in, it does not turn steering ON for the fleet.
// it here would throw that away.
func runSteering(args []string) {
fs := flag.NewFlagSet("steering", flag.ExitOnError)
- app := fs.String("app", "", "app the instance belongs to")
+ app := appFlag(fs, "app the instance belongs to")
host := fs.String("host", "", "instance host to steer")
consoleFlag := fs.String("console", "", "console to act through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, steeringUsage)
fs.Parse(args)
if *host == "" || fs.NArg() != 1 {
log.Fatal(steeringUsage)
@@ -611,12 +617,13 @@ func runSteering(args []string) {
// next reconcile overwrites, and says so.
func runInstancesSetChannel(args []string) {
fs := flag.NewFlagSet("instances set-channel", flag.ExitOnError)
- app := fs.String("app", "", "app the instance belongs to")
+ app := appFlag(fs, "app the instance belongs to")
host := fs.String("host", "", "instance host whose channel to change")
- channel := fs.String("channel", "", "release channel the route should follow")
+ channel := channelFlag(fs, "release channel the route should follow")
consoleFlag := fs.String("console", "", "console to act through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, instancesRecordsUsage)
fs.Parse(args)
if *host == "" || *channel == "" {
log.Fatal(instancesRecordsUsage)
diff --git a/logs.go b/logs.go
index 5f338fc89..937449252 100644
--- a/logs.go
+++ b/logs.go
@@ -13,7 +13,6 @@ import (
"io"
"log"
"net/url"
- "os"
"sort"
"strconv"
"time"
@@ -27,23 +26,6 @@ Shows the newest lines first-to-last: the app's own stdout/stderr ("app")
merged with the platform's events about it ("platform" — wakes, restarts,
failures with the reason). A site served from files alone has no app
stream; platform events still appear.
-
-flags:
- -app <a> app whose logs to read (required)
- -since <t> window start: a duration back from now (15m, 1h, 24h)
- or an RFC3339 time (default 1h)
- -until <t> window end, same forms (default: now)
- -stream <s> only one stream: app | platform | edge | release
- -host <h> only one instance (its route host)
- -level <l> minimum level: debug | info | warn | error — only lines
- that CARRY a level; plain text never claims one
- -grep <re> only lines whose message matches this RE2 pattern
- -field k=v only structured lines with this field (repeatable)
- -n <n> at most this many lines (default 1000, max 5000)
- -f follow: poll for new lines every 2s until interrupted
- -account <a> account (sqid or name) the app belongs to (default "ops")
- -account-id <id> account sqid, matched as a sqid ONLY — supersedes -account
- -console <o> console to read through (default: the only logged-in one)
`
// logsFollowInterval is the -f poll cadence — the serial poll's own 2s, the
@@ -55,7 +37,7 @@ var logsMaxPolls = 0
func runLogs(args []string) {
fs := flag.NewFlagSet("logs", flag.ExitOnError)
- app := fs.String("app", "", "app whose logs to read")
+ app := appFlag(fs, "app whose logs to read")
since := fs.String("since", "1h", "window start: duration back from now, or RFC3339")
until := fs.String("until", "", "window end (default: now)")
stream := fs.String("stream", "", "only one stream: app | platform | edge | release")
@@ -69,7 +51,7 @@ func runLogs(args []string) {
consoleFlag := fs.String("console", "", "console to read through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
- fs.Usage = func() { fmt.Fprint(os.Stderr, logsUsage) }
+ fs.Usage = flagUsage(fs, logsUsage)
fs.Parse(args)
q := url.Values{}
diff --git a/mail_cli.go b/mail_cli.go
index fcfbde984..89cef1ea3 100644
--- a/mail_cli.go
+++ b/mail_cli.go
@@ -596,13 +596,14 @@ func waitForDomainVerified(ctx context.Context, verify func(context.Context) (ap
func runEmailEnable(args []string) {
fs := flag.NewFlagSet("email enable", flag.ExitOnError)
- app := fs.String("app", "", "app to enable email for")
+ app := appFlag(fs, "app to enable email for")
envPrefix := fs.String("env-prefix", "", "env var prefix to deliver under (default CARLOS_SMTP)")
from := fs.String("from", "", "the From address this app sends as (default: noreply@ the domain)")
domain := fs.String("domain", "", "sending domain to ensure and verify (default: the domain half of -from)")
timeout := fs.Duration("timeout", 10*time.Minute, "how long to wait for domain verification before giving up")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to enable through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos email enable -app <a> [-env-prefix <PREFIX>] [-from <addr>] [-domain <d>] [-timeout <dur>] " +
@@ -711,10 +712,11 @@ func runEmailDomains(args []string) {
func runEmailDomainsAdd(args []string) {
fs := flag.NewFlagSet("email domains add", flag.ExitOnError)
- app := fs.String("app", "", "app to add the domain for")
+ app := appFlag(fs, "app to add the domain for")
timeout := fs.Duration("timeout", 10*time.Minute, "how long to wait for verification before giving up")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos email domains add -app <a> [-timeout <dur>] [-account <sqid-or-name>] " +
"[-account-id <sqid>] [-console <origin>] <domain>"
@@ -773,9 +775,10 @@ func printPendingRecords(w io.Writer, recs []apiDNSRecord) {
func runEmailStatus(args []string) {
fs := flag.NewFlagSet("email status", flag.ExitOnError)
- app := fs.String("app", "", "app whose email to show")
+ app := appFlag(fs, "app whose email to show")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos email status -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -936,10 +939,11 @@ func printEmailEnabled(w io.Writer, res apiEmailReply) {
func runEmailTest(args []string) {
fs := flag.NewFlagSet("email test", flag.ExitOnError)
- app := fs.String("app", "", "app to send a test message through")
+ app := appFlag(fs, "app to send a test message through")
to := fs.String("to", "", "address to send the test message to")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
toAddr := strings.TrimSpace(*to)
@@ -1141,10 +1145,11 @@ func runEmailCredentials(args []string) {
func runEmailCredentialsCreate(args []string) {
fs := flag.NewFlagSet("email credentials create", flag.ExitOnError)
- app := fs.String("app", "", "app to mint a standalone credential for")
+ app := appFlag(fs, "app to mint a standalone credential for")
credName := fs.String("name", "", "credential name (lowercase, digits, hyphens)")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos email credentials create -app <a> -name <n> [-account <sqid-or-name>] " +
"[-account-id <sqid>] [-console <origin>]"
@@ -1186,9 +1191,10 @@ func printEmailCredentialsCreate(w io.Writer, res apiEmailCredsReply) {
func runEmailCredentialsList(args []string) {
fs := flag.NewFlagSet("email credentials list", flag.ExitOnError)
- app := fs.String("app", "", "app whose standalone credentials to list")
+ app := appFlag(fs, "app whose standalone credentials to list")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos email credentials list -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -1227,10 +1233,11 @@ func printEmailCredentialsList(w io.Writer, sa []apiEmailStandalone) {
func runEmailCredentialsRevoke(args []string) {
fs := flag.NewFlagSet("email credentials revoke", flag.ExitOnError)
- app := fs.String("app", "", "app whose standalone credential to revoke")
+ app := appFlag(fs, "app whose standalone credential to revoke")
credName := fs.String("name", "", "credential name to revoke")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
usage := "usage: carlos email credentials revoke -app <a> -name <n> [-account <sqid-or-name>] " +
"[-account-id <sqid>] [-console <origin>]"
@@ -1260,10 +1267,11 @@ func runEmailCredentialsRevoke(args []string) {
func runEmailRotate(args []string) {
fs := flag.NewFlagSet("email rotate", flag.ExitOnError)
- app := fs.String("app", "", "app whose delivered email credential to rotate")
+ app := appFlag(fs, "app whose delivered email credential to rotate")
finish := fs.Bool("finish", false, "delete the previous key instead of minting a new one")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos email rotate -app <a> [-finish] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -1306,9 +1314,10 @@ func runEmailPause(args []string, paused bool) {
verb = "resume"
}
fs := flag.NewFlagSet("email "+verb, flag.ExitOnError)
- app := fs.String("app", "", "app to "+verb)
+ app := appFlag(fs, "app to "+verb)
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := fmt.Sprintf("usage: carlos email %s -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]", verb)
diff --git a/pipeline.go b/pipeline.go
index d6cd07d7e..26916a482 100644
--- a/pipeline.go
+++ b/pipeline.go
@@ -20,7 +20,6 @@ import (
"fmt"
"io"
"log"
- "os"
"strings"
)
@@ -117,12 +116,12 @@ func pipelineSignedInSession(consoleFlag string) *apiSession {
// subcommand, not just the one typed.
func pipelineFlagSet(name string) (fs *flag.FlagSet, app, acctRaw, acctIDRaw, consoleFlag *string) {
fs = flag.NewFlagSet(name, flag.ExitOnError)
- app = fs.String("app", "", "app name")
+ app = appFlag(fs, "")
// API mode never opens the local registry — the third return
// (accountFlags' direct-mode resolver) has no use here and is dropped.
acctRaw, acctIDRaw, _ = accountFlags(fs)
consoleFlag = fs.String("console", "", "console origin to work through (default: the only logged-in one)")
- fs.Usage = func() { fmt.Fprint(os.Stderr, pipelineUsage) }
+ fs.Usage = flagUsage(fs, pipelineUsage)
return fs, app, acctRaw, acctIDRaw, consoleFlag
}
@@ -319,7 +318,7 @@ func pipelinePolicyFlags(fs *flag.FlagSet) (bake *string, passkey *bool, promote
// a rule someone else set, and PUTs the whole list back.
func runPipelineSet(args []string) {
fs, app, acctRaw, acctIDRaw, consoleFlag := pipelineFlagSet("pipeline set")
- channel := fs.String("channel", "", "channel to edit")
+ channel := channelFlag(fs, "channel to edit")
bake, passkey, promoteApprovals, changeApprovals := pipelinePolicyFlags(fs)
fs.Parse(args)
usage := "usage: carlos pipeline set -channel <name> [-app <a>] [-bake <dur>] [-passkey[=false]] " +
@@ -382,7 +381,7 @@ func runPipelineSet(args []string) {
// silently made the ENTRY channel, which -after edge would spell instead).
func runPipelineAdd(args []string) {
fs, app, acctRaw, acctIDRaw, consoleFlag := pipelineFlagSet("pipeline add")
- channel := fs.String("channel", "", "channel to add")
+ channel := channelFlag(fs, "channel to add")
after := fs.String("after", "", "insert immediately after this existing channel (default: append at the end)")
bake, passkey, promoteApprovals, changeApprovals := pipelinePolicyFlags(fs)
fs.Parse(args)
@@ -437,7 +436,7 @@ func runPipelineAdd(args []string) {
// as it is the one place every other pipeline shape rule is enforced.
func runPipelineRemove(args []string) {
fs, app, acctRaw, acctIDRaw, consoleFlag := pipelineFlagSet("pipeline remove")
- channel := fs.String("channel", "", "channel to remove")
+ channel := channelFlag(fs, "channel to remove")
fs.Parse(args)
usage := "usage: carlos pipeline remove -channel <name> [-app <a>] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
if *channel == "" {
diff --git a/restart.go b/restart.go
index 213705f78..77e1ccb82 100644
--- a/restart.go
+++ b/restart.go
@@ -22,9 +22,10 @@ import (
// write, and the object it touches means nothing without a fleet reading it.
func runRestart(args []string) {
fs := flag.NewFlagSet("restart", flag.ExitOnError)
- app := fs.String("app", "", "app whose instances to cycle")
+ app := appFlag(fs, "app whose instances to cycle")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to restart through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos restart -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
diff --git a/retention.go b/retention.go
index d31cdfc66..274e95be2 100644
--- a/retention.go
+++ b/retention.go
@@ -21,12 +21,13 @@ import (
func runReleasesRetention(args []string) {
fs := flag.NewFlagSet("releases retention", flag.ExitOnError)
- app := fs.String("app", "", "app whose retention policy to read or write")
+ app := appFlag(fs, "app whose retention policy to read or write")
keep := fs.Int("keep", 0, fmt.Sprintf("retain the newest N releases (minimum %d)", release.KeepFloor))
off := fs.Bool("off", false, "clear the retention policy — keep releases forever again")
dryRun := fs.Bool("dry-run", false, "with -keep: list what would be pruned, write nothing")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos releases retention -app <a> [-keep <n> [-dry-run] | -off] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
diff --git a/routes_cli.go b/routes_cli.go
index a982da920..1671e5a5d 100644
--- a/routes_cli.go
+++ b/routes_cli.go
@@ -37,11 +37,12 @@ Signed-in only (carlos auth login). On a box, the local listing is
func runRoutes(args []string) {
fs := flag.NewFlagSet("routes", flag.ExitOnError)
- app := fs.String("app", "", "app whose routes to list")
+ app := appFlag(fs, "app whose routes to list")
match := fs.String("match", "", "only hosts containing this substring")
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
+ fs.Usage = flagUsage(fs, routesUsage)
fs.Parse(args)
sess := requireAPISession("routes", *consoleFlag)
acct := resolvedAccount(sess, acctRaw, acctIDRaw)
diff --git a/schedule.go b/schedule.go
index 69e809b97..ca350fee4 100644
--- a/schedule.go
+++ b/schedule.go
@@ -105,9 +105,10 @@ func requireScheduleAPISession(consoleFlag string) *apiSession {
func runScheduleLs(args []string) {
fs := flag.NewFlagSet("schedule ls", flag.ExitOnError)
- app := fs.String("app", "", "app whose schedules to list")
+ app := appFlag(fs, "app whose schedules to list")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, scheduleLsUsage)
fs.Parse(args)
sess := requireScheduleAPISession(*consoleFlag)
@@ -123,13 +124,14 @@ func runScheduleLs(args []string) {
func runScheduleSet(args []string) {
fs := flag.NewFlagSet("schedule set", flag.ExitOnError)
- app := fs.String("app", "", "app the schedule belongs to")
+ app := appFlag(fs, "app the schedule belongs to")
name := fs.String("name", "", "schedule name — [a-z0-9][a-z0-9-]{0,31}")
every := fs.String("every", "", "fire every duration, whole minutes only (1m to 720h) — give this or -cron, not both")
cron := fs.String("cron", "", `fire on a 5-field cron expression ("min hour dom mon dow") — give this or -every, not both`)
path := fs.String("path", "", "absolute path on the app's own instance to POST at each fire")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console to record through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, scheduleSetUsage)
fs.Parse(args)
sc := schedule.Schedule{
@@ -171,10 +173,11 @@ func runScheduleSet(args []string) {
func runScheduleRm(args []string) {
fs := flag.NewFlagSet("schedule rm", flag.ExitOnError)
- app := fs.String("app", "", "app the schedule belongs to")
+ app := appFlag(fs, "app the schedule belongs to")
name := fs.String("name", "", "schedule name to remove")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console to remove through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, scheduleRmUsage)
fs.Parse(args)
scheduleName := strings.TrimSpace(*name)
if scheduleName == "" {
@@ -194,10 +197,11 @@ func runScheduleRm(args []string) {
func runScheduleRun(args []string) {
fs := flag.NewFlagSet("schedule run", flag.ExitOnError)
- app := fs.String("app", "", "app the schedule belongs to")
+ app := appFlag(fs, "app the schedule belongs to")
name := fs.String("name", "", "schedule name to run now")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console to run through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, scheduleRunUsage)
fs.Parse(args)
scheduleName := strings.TrimSpace(*name)
if scheduleName == "" {
diff --git a/services.go b/services.go
index d7714e829..e3d164256 100644
--- a/services.go
+++ b/services.go
@@ -155,10 +155,11 @@ func validServiceHandle(handle string) error {
func runServicesCreate(args []string) {
fs := flag.NewFlagSet("services create", flag.ExitOnError)
role := fs.String("role", "", "role: publish, instance, operate or admin (required)")
- app := fs.String("app", "", "bind the credential to one app (default: account-wide)")
+ app := appFlag(fs, "bind the credential to one app (default: account-wide)")
acctRaw := fs.String("account", "", "account (sqid or name) to mint the credential in (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to create it through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
const usageMsg = "usage: carlos services create -role <publish|instance|operate|admin> [-app <app>] " +
"[-account <sqid|name>] [-account-id <sqid>] [-console <origin>] <name>"
@@ -273,6 +274,7 @@ func runServicesList(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) to list credentials for (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to list through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
sess := servicesSignedInSession(*consoleFlag)
@@ -320,6 +322,7 @@ func runServicesRotate(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the credential belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to rotate through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos services rotate [-account <sqid|name>] [-account-id <sqid>] " +
@@ -368,6 +371,7 @@ func runServicesRevoke(args []string) {
acctRaw := fs.String("account", "", "account (sqid or name) the credential belongs to (default: your only account, or the configured/chosen one)")
acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes -account")
consoleFlag := fs.String("console", "", "console to revoke through (default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if fs.NArg() != 1 {
log.Fatal("usage: carlos services revoke [-account <sqid|name>] [-account-id <sqid>] " +
diff --git a/services_cli_test.go b/services_cli_test.go
index c03d213ce..36ef35354 100644
--- a/services_cli_test.go
+++ b/services_cli_test.go
@@ -553,27 +553,37 @@ func parseDeliverCommand(t *testing.T, line string) (verb, flags []string) {
// family's own bare usage constant (secretsUsage etc.), neither of which
// ever prints "Usage of <name>:" for the verb that was actually typed.
func helpOutputIsForVerb(helpOut string, verb []string) bool {
- want := "Usage of " + strings.Join(verb, " ") + ":"
+ // The shared printer's synopsis line: "usage: carlos <verb> ..." —
+ // matched to the verb WORDS, so the top-level page ("usage: carlos
+ // <command> [flags]") can never pass for "secrets set".
+ want := "usage: carlos " + strings.Join(verb, " ")
for _, line := range strings.Split(helpOut, "\n") {
- if strings.TrimSpace(line) == want {
+ trimmed := strings.TrimSpace(line)
+ if trimmed == want || strings.HasPrefix(trimmed, want+" ") {
return true
}
}
return false
}
-// helpOutputHasFlag reports whether helpOut's flag.PrintDefaults output
-// lists EXACTLY flagName as a registered flag — a NAME match, never a
-// substring match (C46, review). Each flag prints as " -name" or " -name
-// type" at the START of its own line, so this checks that shape rather than
+// helpOutputHasFlag reports whether helpOut's flagUsage output lists
+// EXACTLY flagName as a registered flag — a NAME match, never a substring
+// match (C46, review). Each flag prints as " --name <type> usage" (or
+// " --name, -n <type> usage"; single-letter primaries as " -n"), so
+// this compares the row head's dash-stripped first token rather than
// searching for flagName anywhere in the text: a strings.Contains check
// would let a search for -app pass against a renamed -application flag,
// since "-application" begins with the plain substring "-app" — the exact
// false positive the review demonstrated.
func helpOutputHasFlag(helpOut, flagName string) bool {
+ name := strings.TrimLeft(flagName, "-")
for _, line := range strings.Split(helpOut, "\n") {
trimmed := strings.TrimSpace(line)
- if trimmed == flagName || strings.HasPrefix(trimmed, flagName+" ") {
+ // A row's head is "--name" or "-n", optionally ", -alias", then
+ // " <type>". Take the first token and compare the NAME exactly.
+ head, _, _ := strings.Cut(trimmed, " ")
+ head = strings.TrimSuffix(head, ",")
+ if strings.TrimLeft(head, "-") == name && strings.HasPrefix(head, "-") {
return true
}
}
@@ -596,7 +606,7 @@ func TestHelpOutputIsForVerbRejectsFallThrough(t *testing.T) {
t.Fatal("helpOutputIsForVerb matched a fall-through top-level usage page, " +
"which happens to contain \"-app\" as a substring in an unrelated verb's description")
}
- real := "Usage of secrets set:\n -app string\n \tapp name\n"
+ real := "usage: carlos secrets set [flags]\nflags:\n --app, -a <string> app name\n"
if !helpOutputIsForVerb(real, []string{"secrets", "set"}) {
t.Fatal("helpOutputIsForVerb did not match the real usage page for the verb it names")
}
@@ -608,14 +618,14 @@ func TestHelpOutputIsForVerbRejectsFallThrough(t *testing.T) {
// search for -app pass against a hypothetically renamed -application flag,
// since "-application" begins with the plain substring "-app".
func TestHelpOutputHasFlagRejectsPrefixPreservingRename(t *testing.T) {
- renamed := "Usage of secrets set:\n -application string\n \tapp name\n"
+ renamed := "usage: carlos secrets set [flags]\nflags:\n --application <string> app name\n"
if helpOutputHasFlag(renamed, "-app") {
t.Fatal("helpOutputHasFlag(-app) matched -application -- a substring match, not an exact one")
}
if !helpOutputHasFlag(renamed, "-application") {
t.Fatal("helpOutputHasFlag(-application) should match its own exact name")
}
- if !helpOutputHasFlag("Usage of secrets set:\n -app string\n \tapp name\n", "-app") {
+ if !helpOutputHasFlag("usage: carlos secrets set [flags]\nflags:\n --app, -a <string> app name\n", "-app") {
t.Fatal("helpOutputHasFlag(-app) should match its own exact name")
}
}
diff --git a/store.go b/store.go
index c1d99e10b..8adacc55f 100644
--- a/store.go
+++ b/store.go
@@ -126,10 +126,11 @@ func requireStoreAPISession(consoleFlag string) *apiSession {
func runStoreCreate(args []string) {
fs := flag.NewFlagSet("store create", flag.ExitOnError)
- app := fs.String("app", "", "app to declare a store for")
+ app := appFlag(fs, "app to declare a store for")
envPrefix := fs.String("env-prefix", "", "env var prefix to deliver under (default CARLOS_STORE)")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to declare through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos store create -app <a> [-env-prefix <PREFIX>] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -155,9 +156,10 @@ func runStoreCreate(args []string) {
func runStoreStatus(args []string) {
fs := flag.NewFlagSet("store status", flag.ExitOnError)
- app := fs.String("app", "", "app whose store to show")
+ app := appFlag(fs, "app whose store to show")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to read through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos store status -app <a> [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -180,10 +182,11 @@ func runStoreStatus(args []string) {
func runStoreGrant(args []string) {
fs := flag.NewFlagSet("store grant", flag.ExitOnError)
- app := fs.String("app", "", "app to grant a store for")
+ app := appFlag(fs, "app to grant a store for")
bucket := fs.String("bucket", "", "adopt this existing bucket instead of minting one")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to grant through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos store grant -app <a> [-bucket <name>] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
@@ -206,10 +209,11 @@ func runStoreGrant(args []string) {
func runStoreRotate(args []string) {
fs := flag.NewFlagSet("store rotate", flag.ExitOnError)
- app := fs.String("app", "", "app whose store credential to rotate")
+ app := appFlag(fs, "app whose store credential to rotate")
finish := fs.Bool("finish", false, "delete the previous key instead of minting a new one")
acctRaw, acctIDRaw, _ := accountFlags(fs)
consoleFlag := fs.String("console", "", "console origin to rotate through (API mode only; default: the only logged-in one)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
name := strings.TrimSpace(*app)
usage := "usage: carlos store rotate -app <a> [-finish] [-account <sqid-or-name>] [-account-id <sqid>] [-console <origin>]"
diff --git a/update.go b/update.go
index 94f521f47..ed8c89e4f 100644
--- a/update.go
+++ b/update.go
@@ -214,6 +214,7 @@ func installMethod(exe string) string {
func runUpdate(args []string) {
fs := flag.NewFlagSet("update", flag.ExitOnError)
yes := fs.Bool("y", false, "replace without asking (plain-binary installs only)")
+ fs.Usage = flagUsage(fs, "")
fs.Parse(args)
if os.Getenv("CARLOS_DEPLOYMENT_BUCKET") != "" || os.Getenv("CARLOS_DEPLOYMENT_DIR") != "" {
log.Fatal("carlos update is for workstation installs — a box's binary is rolled by update-platform-binary.sh and adoption")