From 75d8b225db2f07b019cf92c110faef5ba164fc87 Mon Sep 17 00:00:00 2001 From: Raj Nakarja Date: Tue, 27 Oct 2026 21:30:50 +0100 Subject: [PATCH] Explain each command in help Help for one command only repeated the overview's summary. Each command can now carry details on what it does that its summary leaves out. --- internal/dispatch/dispatch.go | 10 ++++ internal/dispatch/dispatch_test.go | 5 +- main.go | 76 ++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/internal/dispatch/dispatch.go b/internal/dispatch/dispatch.go index ca7f505..be229a3 100644 --- a/internal/dispatch/dispatch.go +++ b/internal/dispatch/dispatch.go @@ -13,6 +13,7 @@ type Command struct { Name string Arguments string Summary string + Details string Run func(invocation api.Invocation, arguments []string) error } @@ -171,6 +172,15 @@ func Dispatch(sections []Section, version string, arguments []string, in io.Read } fmt.Fprintf(invocation.Out, "superstack %s\n\n %s\n", signature, topic.Summary) + + if topic.Details != "" { + fmt.Fprintln(invocation.Out) + + for _, line := range strings.Split(topic.Details, "\n") { + fmt.Fprintf(invocation.Out, " %s\n", line) + } + } + return nil } diff --git a/internal/dispatch/dispatch_test.go b/internal/dispatch/dispatch_test.go index 781670d..b015242 100644 --- a/internal/dispatch/dispatch_test.go +++ b/internal/dispatch/dispatch_test.go @@ -200,7 +200,7 @@ func TestResolve(t *testing.T) { func TestDispatch(t *testing.T) { sections := []Section{ {Title: "Things", Commands: []Command{ - {Name: "thing list", Arguments: "", Summary: "List a thing", Run: func(invocation api.Invocation, arguments []string) error { + {Name: "thing list", Arguments: "", Summary: "List a thing", Details: "Lists one thing.\nThe id is a number.", Run: func(invocation api.Invocation, arguments []string) error { fmt.Fprintln(invocation.Out, "Listed.") return nil }}, @@ -232,7 +232,8 @@ func TestDispatch(t *testing.T) { {name: "long version flag", arguments: []string{"--version"}, wantOutput: "1.2.3\n"}, {name: "version command", arguments: []string{"version"}, wantOutput: "1.2.3\n"}, {name: "help command", arguments: []string{"help"}, wantOutput: wantHelp}, - {name: "topic help", arguments: []string{"help", "thing", "list"}, wantOutput: "superstack thing list \n\n List a thing\n"}, + {name: "topic help", arguments: []string{"help", "thing", "list"}, wantOutput: "superstack thing list \n\n List a thing\n\n Lists one thing.\n The id is a number.\n"}, + {name: "topic help without details", arguments: []string{"help", "thing", "pending"}, wantOutput: "superstack thing pending\n\n Wait for a thing\n"}, {name: "unknown help topic", arguments: []string{"help", "missing"}, wantError: "unknown command \"missing\"\nRun 'superstack help' for the list."}, {name: "unknown command", arguments: []string{"missing"}, wantError: "unknown command \"missing\"\nRun 'superstack help' for the list."}, {name: "unavailable command", arguments: []string{"thing", "pending"}, wantError: "thing pending is not available yet"}, diff --git a/main.go b/main.go index b6b5d97..a4ae19e 100644 --- a/main.go +++ b/main.go @@ -22,8 +22,11 @@ var sections = []dispatch.Section{ { Title: "Getting started", Commands: []dispatch.Command{ - {Name: "login", Arguments: "", Summary: "Log in with the selected provider", Run: login.Login}, - {Name: "logout", Summary: "Log out of your account", Run: login.Logout}, + {Name: "login", Arguments: "", Summary: "Log in with the selected provider", Run: login.Login, + Details: "Shows a one-time code, then opens the provider's page in your browser\n" + + "to enter it. The login is saved on this computer until you log out."}, + {Name: "logout", Summary: "Log out of your account", Run: login.Logout, + Details: "Ends the login on the server and removes it from this computer."}, }, }, { @@ -32,58 +35,93 @@ var sections = []dispatch.Section{ {Name: "fleet create", Arguments: "", Summary: "Create a fleet", Run: fleet.Create}, {Name: "fleet list", Arguments: "[--json]", Summary: "List the fleets you can reach", Run: fleet.List}, {Name: "fleet rename", Arguments: " ", Summary: "Rename a fleet", Run: fleet.Rename}, - {Name: "fleet transfer", Arguments: " ", Summary: "Hand a fleet to a new owner", Run: fleet.Transfer}, - {Name: "fleet delete", Arguments: "", Summary: "Delete a fleet and unpair its devices", Run: fleet.Delete}, + {Name: "fleet transfer", Arguments: " ", Summary: "Hand a fleet to a new owner", Run: fleet.Transfer, + Details: "The new owner must have logged in at least once. Asks first, because\n" + + "you lose access to the fleet, its devices and its credit."}, + {Name: "fleet delete", Arguments: "", Summary: "Delete a fleet and unpair its devices", Run: fleet.Delete, + Details: "Asks first. Each device wipes its code and waits to be paired again,\n" + + "and any credit left on the fleet is lost."}, }, }, { Title: "Devices", Commands: []dispatch.Command{ - {Name: "device pair", Arguments: " [name]", Summary: "Pair a device with a fleet", Run: device.Pair}, + {Name: "device pair", Arguments: " [name]", Summary: "Pair a device with a fleet", Run: device.Pair, + Details: "The IMEI is the 15-digit number printed on the device. Press the\n" + + "pairing button on the device within 60 seconds of running this."}, {Name: "device list", Arguments: "[fleet_id] [--json]", Summary: "List devices and when they were last seen", Run: device.List}, {Name: "device rename", Arguments: " ", Summary: "Rename a device", Run: device.Rename}, - {Name: "device unpair", Arguments: "", Summary: "Remove a device from its fleet", Run: device.Unpair}, - {Name: "device start", Arguments: "", Summary: "Start the code on a device", Run: device.Start}, - {Name: "device stop", Arguments: "", Summary: "Stop the code on a device", Run: device.Stop}, - {Name: "device restart", Arguments: "", Summary: "Restart the code on a device", Run: device.Restart}, + {Name: "device unpair", Arguments: "", Summary: "Remove a device from its fleet", Run: device.Unpair, + Details: "Asks first. The device wipes its code and waits to be paired again."}, + {Name: "device start", Arguments: "", Summary: "Start the code on a device", Run: device.Start, + Details: "The device starts its code at its next check-in."}, + {Name: "device stop", Arguments: "", Summary: "Stop the code on a device", Run: device.Stop, + Details: "The device stops its code at its next check-in."}, + {Name: "device restart", Arguments: "", Summary: "Restart the code on a device", Run: device.Restart, + Details: "The device restarts its code at its next check-in."}, }, }, { Title: "Files", Commands: []dispatch.Command{ - {Name: "upload", Arguments: " ...", Summary: "Upload files or directories to a device or fleet", Run: files.Upload}, - {Name: "download", Arguments: " ", Summary: "Download the code last uploaded to a device into ", Run: files.Download}, - {Name: "dev", Arguments: " ...", Summary: "Upload on every change, and follow the log from the first upload", Run: dev.Dev}, + {Name: "upload", Arguments: " ...", Summary: "Upload files or directories to a device or fleet", Run: files.Upload, + Details: "Replaces all the code on the device, or on every device now in the\n" + + "fleet. The files must include main.lua. A file goes to the top level of\n" + + "the device under its own name. A directory's contents go to the top\n" + + "level, keeping the folders inside it, except names that start with a\n" + + "dot. Each device picks up the code at its next check-in."}, + {Name: "download", Arguments: " ", Summary: "Download the code last uploaded to a device into ", Run: files.Download, + Details: "Asks before replacing a file that already exists in ."}, + {Name: "dev", Arguments: " ...", Summary: "Upload on every change, and follow the log from the first upload", Run: dev.Dev, + Details: "Uploads the files as upload does, then follows the log of the device or\n" + + "fleet from now on. Each time the files change, it uploads them again.\n" + + "It runs until you press Ctrl-C."}, }, }, { Title: "Logs", Commands: []dispatch.Command{ - {Name: "tail", Arguments: " [imei ...] [-n num [-o num]]", Summary: "Follow a fleet's log as it arrives, or print a window of its past logs with -n", Run: tail.Tail}, + {Name: "tail", Arguments: " [imei ...] [-n num [-o num]]", Summary: "Follow a fleet's log as it arrives, or print a window of its past logs with -n", Run: tail.Tail, + Details: "Shows the fleet's newest 100 logs, then follows new ones until you\n" + + "press Ctrl-C. Name IMEIs after the fleet id to show only those devices.\n" + + "-n num prints the newest num logs, from 1 to 1000, and stops. -o num\n" + + "skips that many of the newest logs first, so -n 100 -o 100 prints the\n" + + "100 before them. Each line shows when the log arrived, the device's\n" + + "IMEI, the kind of log and the device's name, then the text."}, }, }, { Title: "People", Commands: []dispatch.Command{ - {Name: "member add", Arguments: " ", Summary: "Add a member to a fleet", Run: member.Add}, + {Name: "member add", Arguments: " ", Summary: "Add a member to a fleet", Run: member.Add, + Details: "The person must have logged in at least once. A member can do anything\n" + + "with the fleet's devices, but cannot change the fleet itself."}, {Name: "member list", Arguments: " [--json]", Summary: "List a fleet's owner and members", Run: member.List}, - {Name: "member remove", Arguments: " ", Summary: "Remove a member from a fleet", Run: member.Remove}, + {Name: "member remove", Arguments: " ", Summary: "Remove a member from a fleet", Run: member.Remove, + Details: "Asks first. The owner cannot be removed."}, }, }, { Title: "Keys", Commands: []dispatch.Command{ - {Name: "key create", Arguments: "