feat(issue): gh-style issue list, view, create, edit, close and reopen

issue list renders through the shared table: a context line, ID TITLE
LABELS [STATE] [KIND] AUTHOR UPDATED with relative times on a terminal,
the ID colored by state and linked, and a stable tab-separated column
set when piped; empty results say so on stderr. It gains -s -L -l -a -A
-m -S -w, @me, comma-separated label and milestone filters, and --web.

issue view, edit, close and reopen accept #N, OWNER/REPO#N and issue
URLs. Mutations report "✓ Created/Edited/Closed issue alice/x#5 (title)"
on a terminal; issue create prints the new issue's URL on stdout (also
as issue new), and close and reopen skip issues already in that state
with a ! line and take -c/--comment. issue create and edit gain the gh
short flags and singular --add-label, --remove-label, --add-assignee and
--remove-assignee (the plural names still work), and create gains --web.

The interactive create form asks for the title and body, then "What's
next?" (submit, add metadata or cancel), without a confirmation after
it; the edit form names the issue it edits. Every issue command has help
text with examples and completes its flags and issue numbers.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-12 00:15:53 +02:00
1 parent b892423545
commit 0b6232087e
18 files changed
+789 -376

No files matched your search

+10 -10
View File
@@ -43,9 +43,9 @@ func TestUsageErrorsPrecedeRequests(t *testing.T) {
{[]string{"issue", "attachment", "download", "1", "1"}, "supply --output-file, or --output-file - for stdout"},
{[]string{"issue", "attachment", "download", "1", "1", "--json"}, "attachment downloads are binary"},
{[]string{"issue", "attachment", "download", "1", "1", "--output-file", "-", "--json=id"}, "attachment downloads are binary"},
{[]string{"issue", "close", "1", "bad"}, "invalid resource number \"bad\""},
{[]string{"issue", "close", "1", "1", "--no-input"}, "resource 1 is selected more than once"},
{[]string{"issue", "reopen", "1", "01", "--no-input"}, "resource 1 is selected more than once"},
{[]string{"issue", "close", "1", "bad"}, `invalid issue "bad": use a number, #number, OWNER/REPO#number or URL`},
{[]string{"issue", "close", "1", "#1", "--no-input"}, "#1 is selected more than once"},
{[]string{"issue", "reopen", "1", "01", "--no-input"}, "#1 is selected more than once"},
{[]string{"issue", "comment", "create", "1", "--body="}, "comment body cannot be empty"},
{[]string{"issue", "comment", "create", "1", "--body", "text", "--json=title"}, "unknown JSON field \"title\""},
{[]string{"issue", "create", "--no-input"}, "missing required input: supply --title"},
@@ -57,16 +57,16 @@ func TestUsageErrorsPrecedeRequests(t *testing.T) {
{[]string{"issue", "create", "--title", "x", "--attachment", "."}, ". is not a regular file"},
{[]string{"issue", "create", "--milestone-name="}, "--milestone needs a title or ID"},
{[]string{"issue", "edit", "1", "--milestone", "v1", "--remove-milestone"}, "--milestone and --remove-milestone cannot be combined"},
{[]string{"issue", "edit", "1"}, "supply an edit flag"},
{[]string{"issue", "edit", "1", "--editor=false"}, "supply an edit flag"},
{[]string{"issue", "edit", "1", "--add-labels="}, "supply an edit flag"},
{[]string{"issue", "edit", "1"}, "nothing to edit; pass a flag such as --title (see --help)"},
{[]string{"issue", "edit", "1", "--editor=false"}, "nothing to edit"},
{[]string{"issue", "edit", "1", "--add-labels="}, "nothing to edit"},
{[]string{"issue", "edit", "1", "--title="}, "issue title cannot be empty"},
{[]string{"issue", "edit", "1", "--set-assignees", "alice", "--add-assignees", "bob"}, "--set-assignees cannot be combined with --add-assignees or --remove-assignees"},
{[]string{"issue", "edit", "1", "--set-labels", "bug", "--add-labels", "help"}, "--set-labels cannot be combined with --add-labels or --remove-labels"},
{[]string{"issue", "edit", "1", "--set-assignees", "alice", "--add-assignees", "bob"}, "--set-assignees cannot be combined with --add-assignee or --remove-assignee"},
{[]string{"issue", "edit", "1", "--set-labels", "bug", "--add-label", "help"}, "--set-labels cannot be combined with --add-label or --remove-label"},
{[]string{"issue", "edit", "1", "--add-assignees", " "}, "assignee names cannot be empty"},
{[]string{"issue", "edit", "1", "invalid", "--title", "Updated"}, "invalid resource number \"invalid\""},
{[]string{"issue", "edit", "1", "invalid", "--title", "Updated"}, `invalid issue "invalid"`},
{[]string{"issue", "edit", "1", "2", "--editor"}, "--editor requires one issue"},
{[]string{"issue", "edit", "1", "01", "--title", "Updated", "--no-input"}, "resource 1 is selected more than once"},
{[]string{"issue", "edit", "1", "01", "--title", "Updated", "--no-input"}, "#1 is selected more than once"},
{[]string{"issue", "list", "--limit", "0"}, `"-L, --limit" flag: must be 1..1000`},
{[]string{"issue", "list", "--page", "0"}, `"--page" flag: must be a positive integer`},
{[]string{"issue", "list", "--label", "bug, "}, "label names cannot be empty"},