Commit Graph
24 Commits
Author SHA1 Message Date
bircni a47dbc413c fix: address verified review findings (terminal-output-1)
Signed-off-by: bircni <bircni@icloud.com>
2026-10-18 17:38:52 +02:00
bircni d67963b29a fix(terminal): drop escape sequences that markdown character references decode to
Signed-off-by: bircni <bircni@icloud.com>
2026-10-18 17:14:45 +02:00
bircni 1917fc7471 refactor(terminal): inline the column fitting and shorten doc comments
Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 20:49:33 +02:00
bircni 9df6cb7dd3 test: merge duplicate terminal, git and Actions service tests
Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 20:49:32 +02:00
bircni 8bf979099b perf: drop wasted requests and reuse markdown renderers
pr edit with reviewers and other fields reads the pull request once,
org delete counts only repositories, org view counts concurrently,
team list skips the repository count of all-repository teams,
pr diff --web builds the URL locally, pr list re-reads pull requests
concurrently for review fields, and glamour renderers are built once
per width and color.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 18:44:29 +02:00
bircni 83924892ff refactor: reuse the existing service and rendering helpers
services.IsNotFound replaces the hand-rolled 404 checks, convertPage
the copied page conversions and lastPathNumber the comment's own
number parser. The List wrappers around ListPage of releases,
branches, wiki pages, secrets and variables go, pull request filters
resolve milestones through Milestones.FindOne like issues, and
localFlagsChanged, boolOrder, Cell.Render, stateBadge and dueText
replace their copies.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 11:22:52 +02:00
bircni 29f62da006 refactor: load settings once per run and reuse shared helpers
The application caches the settings snapshot and reloads it only after
an update through the store, so output and prompt helpers no longer
read and parse config.yml and hosts.yml several times per command.
canPrompt and chooser replace the inline prompt-policy checks.

Checks are tallied by one services.Tally, terminal.Plural replaces the
two app copies, refKind.article/noun replace the comment group's own
words, list commands use servicePage, and web URLs are built by
repositoryURL and webURL in browser.go.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 10:57:39 +02:00
bircni 1f4b5f868f refactor(prompts): fold Asker into Prompter and share delete confirmations
FormPrompter is the only production prompter and implements the rich
prompt API, so Ask, AskText and ConfirmDefault become the Prompter
interface and the plain Input/Confirm fallbacks go away.

Confirmations that default to no derive their non-terminal wording
from the prompt, and single deletes share deleteConfirmed for the
prompt, the request and the done line.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 10:47:54 +02:00
bircni ea13c9e4f5 refactor: remove features beyond the UX plan
Drop additions that the UX plan does not ask for:

- the interactive label edit form; label edit now requires a flag
- label clone and the skip mode of label creation it needed
- repo rename rewriting the Git remote or tea.repository; it now
  warns that the remote still points to the old name
- the extra "source" remote on clones of pull mirrors
- run artifact download, which duplicates run download --zip
- 256-color and 24-bit label swatches; colors stay ANSI 16
- config list --show-source (config get keeps it)
- the membership and access GETs before team member/repo add and
  remove; Gitea's PUT and DELETE are idempotent

label edit/delete keep --by-name: it is the only way to address a
label whose name is a number that is also another label's ID.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 10:36:50 +02:00
bircni 50a604f169 refactor: remove helpers the new output and prompts left unused
Drop writeDownload, terminal.Require, terminal.Confirm, terminal.Row,
terminal.Record and Pulls.FindOpenByHead, which no command calls anymore,
and point their tests at the code that replaced them.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 10:00:05 +02:00
bircni 5d2f2d83eb feat(label): edit a label in a form without flags
label edit without flags on a terminal now opens a form with the label's
name, color, description and flags, like milestone edit and issue edit,
instead of failing; without a terminal it reports the shared "nothing to
edit" error. The help of issue and milestone close and reopen names its
commands without generating the text from the verb.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 00:17:08 +02:00
bircni c161a63eb6 feat(milestone): milestone tables, views, forms and one selection model
milestone view printed the list row, and milestone list hid the due date
and progress. milestone list now shows ID TITLE [STATE] DUE PROGRESS, open
milestones first and by due date; milestone view shows the state, due
date, progress, description and the milestone's web page (also --web).

Every milestone command addresses milestones by title or ID. Mutations
report ✓ lines: milestone create also prints the new milestone's URL and
takes the title as an argument; close and reopen skip milestones already
in that state; delete names the milestone in its confirmation; milestone
issue add and remove report one line such as "✓ Added #1, #2 to
milestone "v1.0" in alice/x". milestone issue list uses the issue table.
milestone edit without flags on a terminal opens the milestone form with
the current values, and every milestone command completes milestone
titles and has help text with examples.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 00:17:02 +02:00
bircni 0b6232087e 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>
2026-10-12 00:15:53 +02:00
bircni 4895aa089d feat(pr): merge with the repository's defaults, ready, gh flags and ✓ lines for pr mutations
pr merge takes the method from the repository's default merge style
instead of always merging (D22), accepts gh's --merge, --squash and
--rebase next to -s/--method, refuses methods the repository disables,
and adds -d/--delete-branch (defaulting to the repository setting),
--auto, -t, -b, -F and -e. On a terminal it asks for the method and
branch deletion with the defaults preselected, without a picker when
the current branch has a pull request. It reports "✓ Squashed and
merged pull request alice/x#3 (title)", the deleted remote branch, and
switches off and deletes the local head branch; --json returns the
merged pull request. Merged, closed and draft pull requests are
reported before anything is asked. -s without a value points at
--squash.

pr checkout names the local branch after the head branch when that
name is free or already at the head, falling back to OWNER/BRANCH for
forks and pr/NUMBER (D13), fetches and switches quietly, reports
"✓ Checked out #1 into feat/one", and has the alias co. pr clean
reports what it deleted and that a remote branch was already gone.

pr create asks for the title and body, then "What's next?": submit,
submit as a draft, add metadata or change the branches, without the
AGit question up front or a final confirmation. It pushes an unpushed
head with --set-upstream --quiet ("✓ Pushed feat/one to origin"),
prints "✓ Created pull request alice/x#9 (title)" and the URL, and
adds --fill, --web, the alias new, the gh shorthands, and milestones
by title.

New tea pr ready [--undo]. pr close and reopen take -c/--comment,
close takes -d/--delete-branch, and both say when the pull request
already is in that state. pr edit reports "✓ Edited", takes the
singular gh reviewer flags (the plurals still work), milestones by
title and --remove-milestone. pr diff is colored and paged on a
terminal and byte-exact when piped, with --name-only, --color and
--web. Every pull request argument accepts #N, URLs and branch names
and defaults to the current branch's pull request.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:55:11 +02:00
bircni c5cf6b9ed9 feat(pr): review with -a/-r/-c, a review create command and the current branch's PR
tea pr review takes gh's review flags: -a/--approve, -r/--request-changes
and -c/--comment, with -b/--body, -F/--body-file and -e/--editor, and
--pending for a review to submit later. --event is hidden but still
accepted, case-insensitively, as approve, approved, request-changes,
request_changes, comment or pending. tea pr review create is the same
command under the name people look for.

The pull request argument is optional and accepts #N, URLs and branch
names; without one, the pull request of the current branch is reviewed.
Without a review type, tea opens the interactive review on a terminal
and otherwise fails with "choose a review type" instead of posting a
comment. Review types given as a subcommand ("tea pr review approve 13")
point at the flag, and approving or requesting changes on one's own pull
request fails before any editor opens.

The interactive review asks for the type first, hiding Approve and
Request changes for the author, then for an inline body that ctrl+e
opens in the configured editor, then offers to annotate the diff, and
confirms with a default of yes. A declined submission keeps the body as
a draft.

Successful reviews report "✓ Approved pull request alice/x#13 (title)"
on a terminal and print the review URL. review submit finds the
caller's pending review when no ID is given, review list and comments
are tables with headers, and dismiss, undismiss, delete, request and
cancel-request say what they did. Every review command has examples and
completes pull request and review IDs.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:54:45 +02:00
bircni a2bea8da23 feat(auth): guide login, readable status and switch without --host
tea auth login without a method flag asks on a terminal for the instance
(prefilled from the repository, default_host or the only instance), how
to log in (token, browser, SSH key), the pasted token without echo with
a link to the applications page, where to keep it and whether git
should use tea for the instance. Bare hosts are accepted. Logging in to
an account tea already has replaces its credential after a
confirmation, or with --yes; a named account is confirmed before the
token is asked for. Every login ends with "Logged in to HOST as USER
(token, keyring)". OAuth requests write scopes for everything but
administration by default, and --scopes chooses others; --web is an
alias of --oauth.

auth status prints a block per instance with the active account, how
each account authenticates, its expiry, verification and token scopes,
whether git uses tea, and the GITEA_TOKEN of the environment. Piped it
prints tab-separated fields. It fails with exit code 4 when no account
or no usable credential exists instead of printing nothing.

auth switch takes the instance of the repository, --host, default_host
or the sole instance instead of requiring --host, toggles between two
accounts, asks on a terminal with more, and accepts --user. auth logout
takes the account as an argument, names it in its errors, and asks git's
other credential helpers to forget what they stored for the account.
auth refresh points token accounts to tea auth login --with-token, and
the new auth token prints the token of an account for scripts.

Every auth command reports what it did on a terminal and has a long
help text and examples.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:50:44 +02:00
bircni 60de407eca fix(api): filter raw responses with --jq and end output with a newline
tea api /version --jq .version failed with "--jq requires --json", and
--json is rejected for raw responses. api is annotated tea.rawjson now:
--jq filters each successful JSON response, page by page with
--paginate, and prints strings without quotes. A non-JSON response is
an error with --jq; an error response prints its body unfiltered.

JSON responses end with a newline, so an error no longer starts on the
line of the body; on a terminal JSON is indented and colored in the key
order of the response, and other text is sanitized as before. Piped
non-JSON bodies stay byte-exact.

api gains -i for --include and --silent, a Long help modeled on gh api
(placeholders, -f/-F typing, pagination, --jq) and examples, and its
help lists --jq.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:49:54 +02:00
bircni 9f69a89537 feat(run): show runs like gh, watch them live, and download artifacts
run list prints a table with a status icon, the workflow name, title,
branch, event, elapsed time and age, and the total from the server; runs
carry workflow, workflow_path and run_number in JSON. run view shows the
run's state and trigger, its jobs with their steps and durations, the
next steps and the run's URL; --json includes the jobs, --log prints
their logs and --web opens the run.

Run commands accept #N and run URLs, and ask for a recent run in a
terminal when the argument is left out. run watch redraws the run on the
alternate screen, then prints it with a summary; an expired --timeout
names the run and suspects a missing runner when no job started.

run cancel no longer asks for confirmation, rerun refuses unfinished runs
up front, reruns a single job with --job and reports the new attempt,
and delete names the run in its prompt. run logs follows several jobs
with --follow, prefixing lines with the job's name, selects jobs by name
or with --failed, and downloads the archive with --output or --dir.
run download extracts the artifacts of a run by name, and artifact list
shows sizes and expiry.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:46:26 +02:00
bircni ab3e91acd1 fix(webhook): validate events and types, sort events and show delivery URLs
webhook create --events pushh created a webhook that fired on nothing,
since Gitea drops unknown events, and unknown types or a Slack webhook
without a channel failed with raw HTTP 422 errors. --events and --type
now accept only what Gitea knows, list it in help and complete it, and a
Slack webhook needs --config channel=.

Events came back in a different order on every call; they are sorted
now. Webhooks can be told apart again: list, view and JSON show the
delivery URL without user information and query, and without the path
for chat services whose paths carry tokens; view --reveal-url shows it
whole. Tables collapse complete event groups such as pull_request (8).

On a terminal, webhook edit no longer needs an authorization flag: since
Gitea drops the stored header on every edit and never returns it, it asks
for the header again and keeps it, and an empty answer suits webhooks
without one. Without a terminal the edit still requires the flags. Edit
flags lose the create defaults, the unsupported secret flags are hidden
on edit, deletions name the webhook and its scope, and mutations print ✓
lines. terminal.View gains Bodyless for resources without a body.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +02:00
bircni eb51dc8619 fix: report unknown --json fields with the fields a command outputs
tea issue list --json number,titel failed with "accepts 0 arg(s),
received 1": a field list with an unknown name stayed a positional
argument. A field-shaped word after --json now binds to it unless the
command takes the word as an argument, and an unknown field lists the
available fields. --json= is a usage error instead of printing text.

--jq alone now implies --json, except on commands annotated
tea.rawjson that filter raw JSON themselves; -q is short for --jq and
-y for --yes. Fields are listed alphabetically, and terminal.JSONOutput
can indent and color JSON for a terminal.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:55:26 +02:00
bircni 62962b4f70 feat(terminal): add confirmation defaults and fix form input loss
Every confirmation defaulted to No, so Enter after a filled-in form
canceled it; prompts were titled with raw flag names and aborted on an
empty answer; and the shared issue and pull request form lost input:

- Multi-selects hid their last option, so a single assignee looked like
  an empty list and labels showed four of five. They now get an explicit
  height and scroll past eight options.
- A pty without a window size, such as `script` run without a terminal,
  made bubbles panic. Forms now refuse it with "terminal size unknown;
  pass the values as flags or use --no-input".
- Without a configured editor, the form ran to the end and then failed
  with "configure an editor", discarding everything typed. The editor is
  now resolved before the form; without one the body is typed into an
  inline multi-line field.
- The pull request form said "Issue title" and "Issue description". The
  shared fields read "Title (required):" and "Body (markdown):", and the
  review body label gained its missing space.

New prompt API for the area commands: confirmAction with a
terminal.Confirmation (DefaultYes for constructive actions, Typed for
type-to-confirm, NonTTY to name the action in the --yes error) and
requireInput with a human label and inline validation. Declined or
aborted prompts return terminal.ErrCanceled (exit code 3). The existing
confirm and require helpers keep working on top of them, and prompters
without the new terminal.Asker methods keep the plain behavior.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:43:11 +02:00
bircni 55574a9fd0 feat: fit list tables to the terminal and write TSV when piped
Lists had no header, wrapped every row once a title was longer than
the terminal, and were space-padded when piped, so cut -f and awk -F'\t'
could not split them and empty cells collapsed.

terminal.Table now takes column specs and a Display. On a terminal it
prints an uppercase header, aligns columns two spaces apart and shrinks
flexible columns, widest first, to Streams.Width (the terminal, then
$COLUMNS, then 80), cutting by display width with "…". States use the
terminal's ANSI 16 colors, hex colors fall back to the 256-color palette
unless COLORTERM advertises 24-bit, and OSC 8 links appear only on the
first column, independently of color (TEA_HYPERLINKS=auto|always|never).
Piped tables are tab-separated without header, color or links, and times
are RFC3339 UTC instead of "about 2 hours ago".

terminal.View lays out single resources (title, badges, metadata, fields,
body, footer URL) and renders markdown bodies with glamour on a terminal
only (TEA_MARKDOWN=raw keeps them raw). Shared formatters cover relative
times, timestamps, sizes, durations and status icons.

In the app, writeTable and writeView are the new list and view writers:
a "Showing N of M …" context line, an empty-state message on stderr and
the next-page hint. writeList, writeItem and batch keep working and now
render through the new table: tab-separated when piped, truncated at the
widest column on a terminal, with headers for issue list and pr list.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:43:10 +02:00
bircni b86fcb2ff5 fix(pr): strip terminal control sequences from pr diff on a terminal
pr diff copied the server's patch bytes straight to stdout, so a pull
request could change the terminal title, clear the screen or move the
cursor of whoever viewed its diff. Add terminal.SafeWriter, which
filters streamed server text like terminal.SafeText when stdout is a
terminal, and use it for pr diff. Piped output stays byte-exact, so
`tea pr diff N | git apply` keeps working.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:49:14 +02:00
bircni faea86cd94 refactor: add terminal output and forms package (#1124)
- `internal/terminal`: streams and the prompting policy, interactive forms (issue, PR, review, merge, milestone), editor / pager / browser launching, JSON and `--jq` output.
- An aligned table renderer and a record renderer for human output; both strip control and escape characters from server-supplied text so titles and bodies cannot inject terminal sequences.

Reviewed-on: https://gitea.com/gitea/tea/pulls/1124
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
2026-10-11 17:12:23 +00:00