cli: make bundled help self-describing

This commit is contained in:
2026-03-22 23:37:38 +08:00
parent 5859ff219e
commit 4d8c90eb26
49 changed files with 792 additions and 29 deletions
@@ -22,6 +22,12 @@ func newCancelCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "cancel",
Short: "Cancel a thread",
Long: helpLong(
"Use cancel to terminate a thread and record a control message explaining why it should stop.",
"cancel is usually a leader or operator action rather than a normal worker completion step.",
"Use a reason when you need later readers to understand why the thread was stopped.",
),
Example: ` inbox --db .agents/coord.db cancel --agent leader --thread thr_123 --reason "Task superseded by a new plan."`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -22,6 +22,14 @@ func newClaimCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "claim",
Short: "Acquire a lease on a pending thread",
Long: helpLong(
"Use claim to acquire the active worker lease for one thread.",
"claim is the step that turns a candidate thread into owned work.",
"After claim, the worker should inspect the thread, send an in_progress update when real work starts, and finish with done or fail.",
"Only one active lease may exist per thread at a time.",
),
Example: ` inbox --db .agents/coord.db claim --agent worker-a --thread thr_123
inbox --db .agents/coord.db claim --agent worker-a --thread thr_123 --lease-seconds 1800`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -33,6 +33,22 @@ func newCompleteCmd(root *rootOptions, mode string) *cobra.Command {
cmd := &cobra.Command{
Use: mode,
Short: map[string]string{"done": "Mark a thread complete", "fail": "Mark a thread failed"}[mode],
Long: map[string]string{
"done": helpLong(
"Use done to close a claimed thread successfully and record the final result summary.",
"done is a terminal operation; use it only when the work is complete enough to hand back to the leader.",
"Include a short summary and optionally a detailed body or artifacts for the final result.",
),
"fail": helpLong(
"Use fail to close a claimed thread unsuccessfully and record the failure summary.",
"fail is a terminal operation; use it when the attempt cannot complete successfully.",
"Include a short summary and enough detail for the leader to decide on retry, reassignment, or cancellation.",
),
}[mode],
Example: map[string]string{
"done": ` inbox --db .agents/coord.db done --agent worker-a --thread thr_123 --summary "Implemented retry policy" --body "Retries now cover read timeouts."`,
"fail": ` inbox --db .agents/coord.db fail --agent worker-a --thread thr_123 --summary "Blocked by migration issue" --body "Previous schema state is inconsistent."`,
}[mode],
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -23,6 +23,14 @@ func newFetchCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "fetch",
Short: "List candidate threads for an agent",
Long: helpLong(
"Use fetch to discover threads that are candidates for one agent to claim.",
"fetch does not grant ownership; it is only the discovery step before claim.",
"The normal worker loop is fetch -> claim -> show -> update/done/fail.",
"Use --unread when you only want threads whose latest message has not been consumed by that agent.",
),
Example: ` inbox --db .agents/coord.db fetch --agent worker-a --status pending
inbox --db .agents/coord.db fetch --agent worker-a --status pending,blocked --unread --limit 5`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -0,0 +1,24 @@
package inbox
import "strings"
func helpLong(purpose string, constraints ...string) string {
var builder strings.Builder
builder.WriteString(strings.TrimSpace(purpose))
if len(constraints) == 0 {
return builder.String()
}
builder.WriteString("\n\nConstraints:\n")
for _, constraint := range constraints {
constraint = strings.TrimSpace(constraint)
if constraint == "" {
continue
}
builder.WriteString("- ")
builder.WriteString(constraint)
builder.WriteByte('\n')
}
return strings.TrimRight(builder.String(), "\n")
}
@@ -0,0 +1,83 @@
package inbox
import (
"strings"
"testing"
)
func TestInboxRootHelpExplainsWorkerLoop(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeInboxCommand("--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "Workers should use inbox directly") {
t.Fatalf("expected root help to explain worker role, got:\n%s", combined)
}
if !strings.Contains(combined, "Constraints:") {
t.Fatalf("expected root help to include constraints section, got:\n%s", combined)
}
if !strings.Contains(combined, "fetch --agent worker-a --status pending") {
t.Fatalf("expected root help to include a concrete workflow example, got:\n%s", combined)
}
}
func TestInboxUpdateHelpExplainsBlockedQuestions(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeInboxCommand("update", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "Blocked updates should include") {
t.Fatalf("expected update help to explain blocked questions, got:\n%s", combined)
}
if !strings.Contains(combined, "Constraints:") {
t.Fatalf("expected update help to include constraints section, got:\n%s", combined)
}
if !strings.Contains(combined, `--payload-json '{"question":"Use stdout or stderr?"}'`) {
t.Fatalf("expected update help to include payload-json example, got:\n%s", combined)
}
}
func TestInboxWaitReplyHelpExplainsBlockingPrimitive(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeInboxCommand("wait-reply", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "worker-side blocking primitive") {
t.Fatalf("expected wait-reply help to explain its role, got:\n%s", combined)
}
if !strings.Contains(combined, "--after-event 42 --timeout-seconds 900") {
t.Fatalf("expected wait-reply help to include resume example, got:\n%s", combined)
}
}
func TestInboxListHelpExplainsDifferenceFromFetch(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeInboxCommand("list", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "Compared with fetch") {
t.Fatalf("expected list help to explain how it differs from fetch, got:\n%s", combined)
}
if !strings.Contains(combined, "Constraints:") {
t.Fatalf("expected list help to include constraints section, got:\n%s", combined)
}
if !strings.Contains(combined, "--created-by orch --status done,failed") {
t.Fatalf("expected list help to include inspection-focused example, got:\n%s", combined)
}
}
@@ -12,6 +12,12 @@ func newInitCmd(opts *rootOptions) *cobra.Command {
return &cobra.Command{
Use: "init",
Short: "Initialize the shared SQLite database schema",
Long: helpLong(
"Use init to create or migrate the shared inbox SQLite schema at one database path.",
"Run init once before first real use on a new database path.",
"init is safe to rerun when you need to ensure the schema exists before another command.",
),
Example: ` inbox --db .agents/coord.db init`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -23,6 +23,14 @@ func newListCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List threads with filters",
Long: helpLong(
"Use list for broad inbox inspection across many threads.",
"Compared with fetch: fetch is the worker discovery step before claim, while list is the general inspection surface for leaders, operators, and debugging.",
"Use list when you want to scan by assigned worker, creator, or status without implying that the current agent plans to claim the work.",
),
Example: ` inbox --db .agents/coord.db list --status blocked,in_progress
inbox --db .agents/coord.db list --assigned-to worker-a --status pending --limit 10
inbox --db .agents/coord.db list --created-by orch --status done,failed`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -73,11 +81,11 @@ func newListCmd(root *rootOptions) *cobra.Command {
},
}
cmd.Flags().StringVar(&opts.agent, "agent", "", "Assigned agent filter shortcut")
cmd.Flags().StringVar(&opts.statuses, "status", "", "Comma-separated status filter")
cmd.Flags().StringVar(&opts.createdBy, "created-by", "", "Created-by filter")
cmd.Flags().StringVar(&opts.assignedTo, "assigned-to", "", "Assigned-to filter")
cmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of threads")
cmd.Flags().StringVar(&opts.agent, "agent", "", "Shortcut for filtering threads assigned to one agent")
cmd.Flags().StringVar(&opts.statuses, "status", "", "Comma-separated status filter such as pending,blocked,done")
cmd.Flags().StringVar(&opts.createdBy, "created-by", "", "Only include threads created by this agent")
cmd.Flags().StringVar(&opts.assignedTo, "assigned-to", "", "Only include threads currently assigned to this agent")
cmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of threads to return")
return cmd
}
@@ -21,6 +21,13 @@ func newRenewCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "renew",
Short: "Extend an existing lease",
Long: helpLong(
"Use renew to extend an active worker lease on one claimed thread.",
"renew only applies when the caller already owns the active lease.",
"Use renew for long-running work instead of risking lease expiry mid-execution.",
),
Example: ` inbox --db .agents/coord.db renew --agent worker-a --thread thr_123
inbox --db .agents/coord.db renew --agent worker-a --thread thr_123 --lease-seconds 1800`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -27,6 +27,14 @@ func newReplyCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "reply",
Short: "Reply inside an existing thread",
Long: helpLong(
"Use reply to append a directed answer, control note, or follow-up message to an existing thread.",
"Leaders usually use reply or orch answer when a worker is blocked.",
"Workers may also use reply for non-terminal coordination that should stay inside the same thread history.",
"reply is for one existing thread; use send when you need to create a new thread.",
),
Example: ` inbox --db .agents/coord.db reply --from leader --to worker-a --thread thr_123 --summary "Use stdout" --body "Use stdout for MVP."
inbox --db .agents/coord.db reply --from leader --to worker-a --thread thr_123 --kind control --summary "Proceed with option A" --payload-json '{"decision":"option_a"}'`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -14,8 +14,22 @@ func NewRootCmd() *cobra.Command {
opts := &rootOptions{}
cmd := &cobra.Command{
Use: "inbox",
Short: "Worker-facing durable coordination bus",
Use: "inbox",
Short: "Worker-facing durable coordination bus",
Long: helpLong(
"Use inbox to coordinate durable worker-side execution through SQLite-backed threads, messages, leases, and artifacts.",
"Workers should use inbox directly to fetch or inspect one assigned thread, claim it, report progress, ask blocked questions, wait for answers, and finish with done or fail.",
"Leaders usually use orch for planning and dispatch, then use inbox mainly for inspection or manual repair.",
"fetch does not grant ownership; claim is the step that acquires the active lease.",
),
Example: ` inbox --db .agents/coord.db init
inbox --db .agents/coord.db fetch --agent worker-a --status pending
inbox --db .agents/coord.db claim --agent worker-a --thread thr_123
inbox --db .agents/coord.db show --thread thr_123
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status in_progress --summary "Started work"
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status blocked --summary "Need API decision" --payload-json '{"question":"Use v1 or v2?"}'
inbox --db .agents/coord.db wait-reply --thread thr_123 --after-event 10
inbox --db .agents/coord.db done --agent worker-a --thread thr_123 --summary "Finished"`,
SilenceErrors: true,
SilenceUsage: true,
}
@@ -31,6 +31,14 @@ func newSendCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "send",
Short: "Create a thread with an initial directed message",
Long: helpLong(
"Use send to create a new directed thread and first message.",
"This is the low-level thread creation primitive.",
"Leaders often create task threads through orch dispatch instead; use inbox send for manual repair, ad hoc coordination, or a thread that does not belong to an orch-managed task.",
"Creating a new thread requires a subject unless you are explicitly targeting an existing thread ID.",
),
Example: ` inbox --db .agents/coord.db send --from leader --to worker-a --subject "Investigate flaky test" --summary "Check latest failures" --run run_blog_001 --task T1
inbox --db .agents/coord.db send --from leader --to worker-a --subject "Review logs" --summary "See attached stacktrace" --body-file ./brief.md --artifact ./failure.log --artifact-kind log`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -20,6 +20,14 @@ func newShowCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Show one thread with message history",
Long: helpLong(
"Use show to inspect one thread together with its message history.",
"Workers should usually inspect the assigned thread before making assumptions about task body, payload JSON, or prior coordination.",
"Leaders can also use show for manual debugging and repair.",
"Use show when you need the exact message sequence inside one thread, not just a filtered thread list.",
),
Example: ` inbox --db .agents/coord.db show --thread thr_123
inbox --db .agents/coord.db --agent worker-a show --thread thr_123 --mark-read`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -71,7 +79,7 @@ func newShowCmd(root *rootOptions) *cobra.Command {
}
cmd.Flags().StringVar(&opts.threadID, "thread", "", "Thread ID")
cmd.Flags().BoolVar(&opts.markRead, "mark-read", false, "Advance the caller's read cursor to the latest message")
cmd.Flags().BoolVar(&opts.markRead, "mark-read", false, "Advance the caller's read cursor to the latest message in this thread")
_ = cmd.MarkFlagRequired("thread")
return cmd
@@ -26,6 +26,14 @@ func newUpdateCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "update",
Short: "Append a progress or blocked update to a thread",
Long: helpLong(
"Use update to append worker progress inside one claimed thread.",
"in_progress means real work has started.",
"blocked means the worker cannot continue without a precise answer or dependency.",
"Blocked updates should include a concise summary and usually a payload_json question so the leader can answer deterministically.",
),
Example: ` inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status in_progress --summary "Reading current auth flow"
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status blocked --summary "Need logging decision" --payload-json '{"question":"Use stdout or stderr?"}'`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -24,6 +24,15 @@ func newWaitReplyCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "wait-reply",
Short: "Block until a reply-like message appears in a thread",
Long: helpLong(
"Use wait-reply after a worker has marked a thread blocked and needs a leader response.",
"This is the worker-side blocking primitive; prefer it over ad hoc sleep loops.",
"Resume with --after-event or --after-message when you already know the last event or message you processed.",
"wait-reply watches one thread, unlike watch which can observe broader inbox activity.",
),
Example: ` inbox --db .agents/coord.db wait-reply --thread thr_123
inbox --db .agents/coord.db wait-reply --thread thr_123 --after-event 42 --timeout-seconds 900
inbox --db .agents/coord.db wait-reply --thread thr_123 --kinds answer,result`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -23,6 +23,13 @@ func newWatchCmd(root *rootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "watch",
Short: "Block until new matching activity appears",
Long: helpLong(
"Use watch to wait for new matching thread activity across the inbox.",
"This is useful for operator-style inspection or lightweight agents that need to observe when new work, blocked work, or terminal results appear without polling manually.",
"watch is broader than wait-reply: it watches many matching threads, while wait-reply is the worker-side primitive for one blocked thread.",
),
Example: ` inbox --db .agents/coord.db watch --status pending,blocked
inbox --db .agents/coord.db watch --agent worker-a --status pending --after-event 100 --timeout-seconds 300`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -81,10 +88,10 @@ func newWatchCmd(root *rootOptions) *cobra.Command {
},
}
cmd.Flags().StringVar(&opts.agent, "agent", "", "Assigned agent filter")
cmd.Flags().StringVar(&opts.statuses, "status", "pending,blocked,done,failed", "Comma-separated status filter")
cmd.Flags().IntVar(&opts.timeoutSeconds, "timeout-seconds", 0, "Maximum time to wait; 0 waits forever")
cmd.Flags().Int64Var(&opts.afterEventID, "after-event", 0, "Resume after a known event ID")
cmd.Flags().StringVar(&opts.agent, "agent", "", "Only wake for threads assigned to this agent")
cmd.Flags().StringVar(&opts.statuses, "status", "pending,blocked,done,failed", "Comma-separated thread status filter")
cmd.Flags().IntVar(&opts.timeoutSeconds, "timeout-seconds", 0, "Maximum time to wait; 0 means wait forever")
cmd.Flags().Int64Var(&opts.afterEventID, "after-event", 0, "Resume after a known inbox event ID")
return cmd
}