// 🤖 The `lchat` command surface: the subcommand list `lchat help` prints, and // the guard that stops an unrecognised word from silently starting a server. // // Why the guard exists: main()'s dispatch falls through to "run a single // instance" for anything it doesn't recognise, because a bare `lchat` plus // flags is a valid, supported invocation. That is right for flags and wrong // for words — a typo (`lchat routes`, `lchat help`) used to boot a full // messenger server rather than say "no such command". Usually it died on a // port clash and looked like a confusing error; when the port was free it kept // running, which is how a stray `lchat help` process sat on a production box // for five days. // // So: a first argument starting with "-" is a flag (fall through, unchanged), // and anything else must name a subcommand or we print usage and exit 2. package main import ( "fmt" "io" "os" "strings" ) // subcommand is one row of `lchat help`. The blurb is one line, imperative, // and describes the verb — flags belong to `lchat -h`. type subcommand struct{ name, blurb string } // subcommands is what `lchat help` prints, in the order it prints them — // grouped by what an operator is trying to do rather than alphabetically, // since the list is long enough that alphabetical hides the shape. // // main()'s switch is the REAL dispatcher; this is only the human-facing list. // TestUsageCoversEverySubcommand reads main.go and pins the two together, so a // new `case` that nobody documents (or a documented verb that doesn't // dispatch) fails the tests rather than drifting quietly. var subcommands = []subcommand{ // Long-running processes: the front door and the sibling services. {"router", "run the front door (TLS termination + host routing)"}, {"controld", "run the local control daemon (root-only provisioning API)"}, {"cloud", "run the public self-serve space provisioner"}, {"tunnel", "run the trial-tunnel gateway that fronts `lchat try`"}, {"links", "run the short-link redirector (`links run`)"}, {"fetch", "run the public-web fetch service: unfurl + signed image proxy (`fetch run`)"}, {"hooks", "run the webhook-ingest sidecar (`hooks run`)"}, {"bot", "run the bot sidecar (`bot run`)"}, // Standing a box up, and putting things on it. {"bootstrap", "install/refresh this box's systemd units, /data, and the router"}, {"add-instance", "create a chat instance and route it"}, {"add-service", "front an already-running backend on a unix socket"}, {"add-bot", "provision an instance's bot sidecar"}, {"add-hooks", "provision an instance's webhook-ingest sidecar"}, // Shipping code: the versioned, side-by-side deploy path. {"stage", "register a built binary in the version store"}, {"install", "put a staged build onto one instance without restarting it"}, {"deploy", "repoint an instance at a version and restart (health-checked)"}, {"rollback", "step an instance back to its previous version"}, {"init-versions", "one-time adoption of the version store on an old box"}, {"update", "the release-channel auto-updater (`update status`)"}, {"release", "the signing side of the release feed (keygen/sign/verify)"}, // Looking after what's already running. {"instances", "list every instance and service on this box"}, {"env", "amend an instance's env in place (`env set |--all KEY=VALUE`)"}, {"remove", "remove an instance (deliberately leaves /data behind)"}, {"reset", "archive a canary's data (canary-flag gated)"}, {"canary", "set/clear an instance's throwaway-canary flag"}, {"hibernate", "flip an instance between always-on and hibernating"}, {"replication", "read or set one instance's replication class"}, {"relocate", "move an instance to another region"}, {"mesh-seed", "bulk-replicate every space from a peer box"}, {"admin-link", "print an instance's admin link"}, {"setup-invite", "mint an instance's one-time setup link"}, // Local development. {"try", "expose a local server at a real public origin"}, {"relay", "run a space through the tunnel gateway"}, {"version", "print this binary's build id"}, } // isSubcommand reports whether arg names a subcommand. Kept separate from the // dispatch switch so it is testable without running main(). func isSubcommand(arg string) bool { for _, c := range subcommands { if c.name == arg { return true } } return false } // looksLikeFlag reports whether arg is a flag rather than a mistyped // subcommand. A bare "-" is not a flag — nothing here reads stdin, so it is a // typo, and naming it as one beats letting the flag package complain. func looksLikeFlag(arg string) bool { return strings.HasPrefix(arg, "-") && arg != "-" } // writeUsage prints the command surface. Instance flags are deliberately NOT // listed: they belong to the no-subcommand form, and `lchat -h` still prints // them via the flag package exactly as it always has. func writeUsage(w io.Writer) { fmt.Fprint(w, "lchat — the Eleven messenger server, router, and operator CLI.\n\n"+ "Usage:\n"+ " lchat [flags] run a command (see below)\n"+ " lchat [flags] run a single instance directly (`lchat -h` for its flags)\n\n"+ "Commands:\n") width := 0 for _, c := range subcommands { if len(c.name) > width { width = len(c.name) } } for _, c := range subcommands { fmt.Fprintf(w, " %-*s %s\n", width, c.name, c.blurb) } fmt.Fprint(w, "\nRun `lchat -h` for a command's own flags.\n") } // checkSubcommand guards main()'s dispatch. It is called only when the switch // found no match, so reaching here with a non-flag word means the word names // nothing — print usage and exit rather than falling through and starting a // server. `help` is handled here too (it is a word, not a flag, so it used to // boot a server as well). func checkSubcommand(arg string) { if arg == "help" { writeUsage(os.Stdout) os.Exit(0) } if looksLikeFlag(arg) { return // a flag: fall through to the single-instance form, unchanged } fmt.Fprintf(os.Stderr, "lchat: unknown command %q\n\n", arg) writeUsage(os.Stderr) os.Exit(2) }