eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// πŸ€– The `lchat` command surface: the subcommand list `lchat help` prints, and
2// the guard that stops an unrecognised word from silently starting a server.
3//
4// Why the guard exists: main()'s dispatch falls through to "run a single
5// instance" for anything it doesn't recognise, because a bare `lchat` plus
6// flags is a valid, supported invocation. That is right for flags and wrong
7// for words β€” a typo (`lchat routes`, `lchat help`) used to boot a full
8// messenger server rather than say "no such command". Usually it died on a
9// port clash and looked like a confusing error; when the port was free it kept
10// running, which is how a stray `lchat help` process sat on a production box
11// for five days.
12//
13// So: a first argument starting with "-" is a flag (fall through, unchanged),
14// and anything else must name a subcommand or we print usage and exit 2.
15package main
16
17import (
18 "fmt"
19 "io"
20 "os"
21 "strings"
22)
23
24// subcommand is one row of `lchat help`. The blurb is one line, imperative,
25// and describes the verb β€” flags belong to `lchat <name> -h`.
26type subcommand struct{ name, blurb string }
27
28// subcommands is what `lchat help` prints, in the order it prints them β€”
29// grouped by what an operator is trying to do rather than alphabetically,
30// since the list is long enough that alphabetical hides the shape.
31//
32// main()'s switch is the REAL dispatcher; this is only the human-facing list.
33// TestUsageCoversEverySubcommand reads main.go and pins the two together, so a
34// new `case` that nobody documents (or a documented verb that doesn't
35// dispatch) fails the tests rather than drifting quietly.
36var subcommands = []subcommand{
37 // Long-running processes: the front door and the sibling services.
38 {"router", "run the front door (TLS termination + host routing)"},
39 {"controld", "run the local control daemon (root-only provisioning API)"},
40 {"cloud", "run the public self-serve space provisioner"},
41 {"tunnel", "run the trial-tunnel gateway that fronts `lchat try`"},
42 {"links", "run the short-link redirector (`links run`)"},
43 {"fetch", "run the public-web fetch service: unfurl + signed image proxy (`fetch run`)"},
44 {"hooks", "run the webhook-ingest sidecar (`hooks run`)"},
45 {"bot", "run the bot sidecar (`bot run`)"},
46 {"agents", "run the workstation bridge for shared Claude Code sessions (`agents run`)"},
47
48 // Standing a box up, and putting things on it.
49 {"bootstrap", "install/refresh this box's systemd units, /data, and the router"},
50 {"add-instance", "create a chat instance and route it"},
51 {"add-service", "front an already-running backend on a unix socket"},
52 {"add-bot", "provision an instance's bot sidecar"},
53 {"add-hooks", "provision an instance's webhook-ingest sidecar"},
54
55 // Shipping code: the versioned, side-by-side deploy path.
56 {"stage", "register a built binary in the version store"},
57 {"install", "put a staged build onto one instance without restarting it"},
58 {"deploy", "repoint an instance at a version and restart (health-checked)"},
59 {"rollback", "step an instance back to its previous version"},
60 {"init-versions", "one-time adoption of the version store on an old box"},
61 {"update", "the release-channel auto-updater (`update status`)"},
62 {"release", "the signing side of the release feed (keygen/sign/verify)"},
63
64 // Looking after what's already running.
65 {"instances", "list every instance and service on this box"},
66 {"env", "amend an instance's env in place (`env set <host>|--all KEY=VALUE`)"},
67 {"remove", "remove an instance (deliberately leaves /data behind)"},
68 {"reset", "archive a canary's data (canary-flag gated)"},
69 {"canary", "set/clear an instance's throwaway-canary flag"},
70 {"hibernate", "flip an instance between always-on and hibernating"},
71 {"replication", "read or set one instance's replication class"},
72 {"relocate", "move an instance to another region"},
73 {"mesh-seed", "bulk-replicate every space from a peer box"},
74 {"admin-link", "print an instance's admin link"},
75 {"setup-invite", "mint an instance's one-time setup link"},
76
77 // Local development.
78 {"try", "expose a local server at a real public origin"},
79 {"relay", "run a space through the tunnel gateway"},
80 {"version", "print this binary's build id"},
81}
82
83// isSubcommand reports whether arg names a subcommand. Kept separate from the
84// dispatch switch so it is testable without running main().
85func isSubcommand(arg string) bool {
86 for _, c := range subcommands {
87 if c.name == arg {
88 return true
89 }
90 }
91 return false
92}
93
94// looksLikeFlag reports whether arg is a flag rather than a mistyped
95// subcommand. A bare "-" is not a flag β€” nothing here reads stdin, so it is a
96// typo, and naming it as one beats letting the flag package complain.
97func looksLikeFlag(arg string) bool { return strings.HasPrefix(arg, "-") && arg != "-" }
98
99// writeUsage prints the command surface. Instance flags are deliberately NOT
100// listed: they belong to the no-subcommand form, and `lchat -h` still prints
101// them via the flag package exactly as it always has.
102func writeUsage(w io.Writer) {
103 fmt.Fprint(w, "lchat β€” the Eleven messenger server, router, and operator CLI.\n\n"+
104 "Usage:\n"+
105 " lchat <command> [flags] run a command (see below)\n"+
106 " lchat [flags] run a single instance directly (`lchat -h` for its flags)\n\n"+
107 "Commands:\n")
108 width := 0
109 for _, c := range subcommands {
110 if len(c.name) > width {
111 width = len(c.name)
112 }
113 }
114 for _, c := range subcommands {
115 fmt.Fprintf(w, " %-*s %s\n", width, c.name, c.blurb)
116 }
117 fmt.Fprint(w, "\nRun `lchat <command> -h` for a command's own flags.\n")
118}
119
120// checkSubcommand guards main()'s dispatch. It is called only when the switch
121// found no match, so reaching here with a non-flag word means the word names
122// nothing β€” print usage and exit rather than falling through and starting a
123// server. `help` is handled here too (it is a word, not a flag, so it used to
124// boot a server as well).
125func checkSubcommand(arg string) {
126 if arg == "help" {
127 writeUsage(os.Stdout)
128 os.Exit(0)
129 }
130 if looksLikeFlag(arg) {
131 return // a flag: fall through to the single-instance form, unchanged
132 }
133 fmt.Fprintf(os.Stderr, "lchat: unknown command %q\n\n", arg)
134 writeUsage(os.Stderr)
135 os.Exit(2)
136}
137