Commit Graph
824 Commits
Author SHA1 Message Date
bircni 4edb836416 feat(workflow): address workflows by name and check dispatch inputs
Workflows are found by file name, file name without extension, path or
name, case-insensitively, with the candidates listed when a name is
ambiguous. workflow list shows a NAME, FILE and STATE table, and workflow
view shows the workflow with its last runs, prints its file with --yaml
(at --ref) or opens it with --web.

workflow run reads the workflow_dispatch inputs of the workflow at the
ref and refuses unknown inputs, missing required inputs and values a
choice, boolean or number input does not accept, before dispatching. In
a terminal it asks for missing inputs, or all of them when none is given.
Without --ref it uses the checked-out branch, which must be on the
server, or the default branch when the repository does not come from the
working directory. It reports "Dispatched CI (ci.yml) on main → run 33"
with the command to watch it, prints the run's URL, and --follow follows
the logs of all jobs within --timeout.

workflow enable and disable no longer ask for confirmation, report the
change, warn when there is nothing to do, and return the workflow with
--json.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:46:26 +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 b09c1aab4a fix(run): show logs past jobs that have not started
run logs stopped at the first job without logs with a misleading not-found
error. Report such a job as "build (job 35): not started yet" (or that it
never started when it was canceled or skipped first) and continue with the
next job. Jobs report started_at and completed_at, null until they happen.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:46:26 +02:00
bircni 68da491b9e fix(run): filter runs by creation time and report unstarted times as null
run list --since/--until compared the start time, which queued runs report
as the Unix epoch, so every queued run was dropped. Filter on created_at,
stop scanning at the first older run, and report a run's missing start or
completion time as null instead of 1970-01-01.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:46:26 +02:00
bircni f21c05ead4 fix(ssh-key): title keys by their comment and recognize private keys
ssh-key create - required --title although the key carries a comment,
and a private key failed with "supply exactly one valid SSH public key".
The title now defaults to the key comment, then the computer's name, the
file argument is optional (stdin when piped), and a private key points to
its .pub file. Adding a registered key names the key it duplicates.

ssh-key list shows ID, TITLE, FINGERPRINT and ADDED with a header, view
shows the key instead of its list row, delete asks with the key's title
and fingerprint, and mutations print ✓ lines. create gains the gh alias
add and -t for --title, key IDs complete, and every command has examples.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +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 769e93f5fd fix(team): take team repositories as arguments and address teams by name
team repo add 5 org/repo was rejected, and team repo add 5 silently used
the repository of the current directory and failed with an internal
error. team repo add and remove now take the repositories as arguments,
resolve bare names inside the team's organization, refuse repositories of
other owners and --repo, and never discover a repository.

Teams can be addressed as ORG/TEAM by name or ID as well as by their
global ID. team list drops the always-empty ORG column and shows the
derived access ("write (code, issues, pulls)" instead of none), member
and repository counts; team view shows units, members and repositories.
--permission read or write now grants every repository unit on both
create and edit, and edit no longer shows the create default. team
create takes the name as ORG/NAME or a second argument. Member and
repository changes skip users and repositories already in the wanted
state with a warning, name them in confirmations and print ✓ lines. Every
command has examples, and teams, members, users, repositories and --unit
values complete.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +02:00
bircni c18dae6685 fix(admin): offer --purge for users who own repositories and show roles
admin user delete of a user who owns repositories failed with Gitea's raw
HTTP 422. The error now says what blocks the deletion and how to proceed,
and --purge deletes the account with its repositories, packages and
organization memberships after typing the username on a terminal.

admin user list shows ROLE and STATUS columns (admin, restricted, user;
active, inactive, login prohibited) with the email and creation date
instead of admin=true/active=true cells, and --query filters by username,
name or email. admin user view shows the account instead of its list
row. create asks for a missing username and email address with readable
labels and re-asks for invalid answers, mutations print ✓ lines, usernames
complete, and every command has examples.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +02:00
bircni ef8cfbc4b7 fix(org): check membership before member changes and polish org output
org member remove and public/private exited 0 without a word for users
who are not members, and member list --public showed concealed members,
because Gitea lists them as public to administrators. Membership changes
now check the membership first: removing a non-member warns, publicizing
one fails, and a change to the current visibility warns. Member list
checks each membership and gains a VISIBILITY column.

org list and member list get a header and context line through
writeTable, org view shows the description, website, location and member,
team and repository counts, mutations print ✓ lines, and org delete asks
to type the organization's name after refusing organizations that still
own repositories. public and private become publicize and conceal, the
old names stay as aliases, and org edit --visibility no longer shows the
create default. Every command has examples, and organizations and members
complete.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +02:00
bircni 48a188ceac fix(app): pretty-print JSON on a terminal and reconcile the foundation streams
writeJSON uses the indented, colored JSON writer when stdout is a
terminal. Help and JSON tests now expect the -L shorthand column and
compare terminal JSON without color sequences.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:57:38 +02:00
bircni 04d5f2aed0 feat: suggest similar flags for an unknown flag
tea issue list --lable bug now fails with "unknown flag: --lable; did
you mean --label?". Suggestions are the command's visible flags that
start with the typed name or are at most two edits away from it.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:55:27 +02:00
bircni f03d0fdf54 feat(completion): complete flag values and stop offering file names
Every value completed to file names, because no command registered a
completion. Values without a completer now complete nothing, and only
path flags (--*-file, --input, --attachment, --asset, --file, --ssh-key,
--dir, --cwd) and arguments named file or directory complete paths.

--json completes the fields of the command, also after a comma. --host,
--user, --remote and -R complete from hosts.yml, Git remotes and the
instance. enumFlag and enumValues give a flag one list of values that
validation (case-insensitive) and completion share, and instance
completers for issues, pull requests, labels, milestones, users,
branches, tags, releases, organizations, teams, workflows, runs, secrets
and variables are ready for commands to attach. They give up after two
seconds, never prompt, retry or refresh an OAuth token, and drop values
with control characters.

tea completion bash now says to use eval "$(tea completion bash)",
because bash 3.2 on macOS cannot source <(...), and names the
bash-completion package; the zsh help says to run compinit first. The
README matches.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:55:27 +02:00
bircni 48c1e5b781 feat(help): group root help, show relevant flags and add help topics
The root help lists commands in gh-style groups (core, Actions,
additional and alias commands) followed by help topics, examples and
pointers. Every help page uses USAGE, ALIASES, FLAGS, INHERITED FLAGS,
ARGUMENTS, JSON FIELDS, EXAMPLES and LEARN MORE sections. Inherited
flags show --repo, --remote and --cwd only where a repository is
selected, --json and --jq where JSON is printed, --yes where the
command confirms, and one line for the remaining global flags.

tea help environment, exit-codes and formatting document global flags,
environment variables, exit codes and JSON output. tea help with an
unknown topic is a usage error with suggestions instead of printing the
root help. A test keeps Shorts, examples and Long texts complete, with
allowlists for the commands that still lack them.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:55:27 +02:00
bircni f6a0bcfa6a feat: accept ls and rm for list and delete commands
Every list command also answers to ls and every delete command to rm,
as in gh. flagAliases lets a command accept old flag names for renamed
flags without listing them in help, so singular gh names such as
--add-label can become primary while the plural names keep working.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:55:26 +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 023724d4c9 refactor(app): name the feedback label helper refLabel
The reference parser uses issueRef as a reference kind.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:49:40 +02:00
bircni 71189fd15f fix: tighten error classification and pull request selection
Only Gitea's archived-repository messages mean the repository is archived,
so an archived label is no longer reported as one. Refused POSTs to the
organization, team, user and SSH key collections name the resource they would
have created, and the existing pull request is looked up with escaped branch
names.

pullSelector without an argument refuses an explicit --repo, like gh: the
checked-out branch says nothing about another repository.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:50 +02:00
bircni dc43272085 feat: explain refusals to delete or rerun an unfinished run
Gitea refuses to delete or rerun a run that has not finished with "this
workflow run is not done". Report "run 37 has not finished; cancel it first:
tea run cancel 37", and cover the next steps added to refusals in tests.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:50 +02:00
bircni 6fe72cdfd1 fix(app): publish downloads with umask permissions and add destination flags
Downloads were created like temporary files, readable by their owner only.
They now get the permissions the umask allows, as files a shell redirection
creates; API responses written with --output-file stay private. An
--output-file that is a directory is reported as such instead of "output
file already exists", and a download written to a terminal refuses binary
data and strips control sequences from text.

downloadFlags gives download commands the gh destination flags: -O/--output
(a file, a directory, or - for stdout; --output-file stays as a hidden alias),
-D/--dir, --clobber and --skip-existing, with the download's own name as the
default destination. Names that would escape the destination are refused.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:50 +02:00
bircni 1505df2bb1 feat(app): parse issue, pull request and run references
resolveRef accepts N, #N, [HOST/]OWNER/REPO#N and issue, pull request or run
URLs; a URL or qualified number selects its repository for the invocation and
must agree with --repo. resolveRefs does the same for several references.

pullSelector also accepts a head branch or OWNER:BRANCH and, without an
argument, finds the pull request of the checked-out branch: the one pr
checkout recorded in branch.<name>.tea-pr, else the open pull request whose
head is the branch's upstream in the upstream remote's repository, else one
whose head has the local branch's name, else N of a pr/N branch. This lets pr
commands find the pull request of a branch pr checkout created.

target.expandMe resolves @me in user-valued flags to the login tea acts as,
and git.CurrentBranchConfig reads the branch configuration involved.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:50 +02:00
bircni 2ef6090734 feat(app): show the usage line on argument errors and allow -L up to 1000
Positional argument errors name what is wrong from the command's usage, then
print the usage line and where to find help:

  missing issue number
  Usage: tea issue view <number> [flags]
  Run 'tea issue view --help' for more information.

instead of "accepts 1 arg(s), received 0". Resource numbers accept #N.

--limit gains the gh shorthand -L and accepts up to 1000 results; the list
services already page through the server's smaller pages to fill a window.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:49 +02:00
bircni 9b1d3a61e6 feat: explain failed requests and exit silently on interrupt
Failures of tea's own API requests now name what was missing or refused
instead of "resource or API endpoint unavailable":

  issue #999 not found in alice/project
  repository alice/nope not found on http://localhost:3001 (or you don't have access)
  issues are disabled for bob/project
  #1 in alice/project is an issue, not a pull request; view it with: tea issue view 1
  alice/old-stuff is archived (read-only); unarchive it with: tea repo edit ...

A 404 on a repository endpoint looks the repository up once to tell a missing
or hidden repository from a disabled feature, an empty repository or an issue
number given to a pull request command. Refusals get their next step, a
rejected GITEA_TOKEN and unreachable servers are named, and timeoutError words
an expired --timeout. Errors a command already wrapped with its own context,
and raw tea api responses, keep their wording.

Ctrl-C and SIGTERM exit with 130 without printing an error, and a declined
confirmation prints "Canceled." with exit code 3.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:49 +02:00
bircni 2d4d34ddfc feat: accept repository shorthands and name instances in account errors
Repository arguments and --repo accept NAME (owned by the active account, or
the --sudo user), OWNER/NAME.git and HOST/OWNER/NAME next to URLs; --host,
TEA_HOST and GITEA_INSTANCE_URL accept a bare HOST[:PORT][/PATH], which takes
the scheme of the configured instance it names, else https. --repo origin in a
clone with that remote suggests --remote origin.

Account errors name the instance, the accounts that are logged in and where
the instance came from, such as "no account for https://gitea.com (instance
from git remote \"origin\")", instead of "no selected account".

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:49 +02:00
bircni c8073f1b2e feat(services): classify API failures by resource and report page totals
Add NotFoundError, ConflictError and AmbiguousError so commands can report
"issue #999 not found in alice/project" or "alice/old-stuff is archived"
instead of the transport's status text. Classify wraps a failure with the
resource the caller names; ClassifyRequest infers it from the endpoint for
failures no service classified, and maps the refusals Gitea explains (archived
repository, merged or draft pull request, own review, existing resource,
unknown user, disabled workflow, completed run, reused SSH key).

Page reports the X-Total-Count that listWindow discarded, NameOrID resolves a
resource by ID or name (numeric values are IDs first, ambiguous names list
their IDs), and rawPage pages raw collection endpoints.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:49 +02:00
bircni 0254c0509e fix(transport): keep harmless query values visible in --debug output
--debug replaced every query value with REDACTED, so it could not show which
filters were sent. Redact only parameters that can authenticate a request
(tokens, passwords, secrets, signatures, storage credentials, OAuth codes and
the OAuth state), and log the sanitized server message of failed responses.

A 404 now names the request instead of blaming the instance, and API errors
recorded from a request keep its instance and credential so a caller can look
up why it failed. Network failures keep the server they could not reach.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:48:49 +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 4fddcc6781 feat(app): report mutations on stderr and say what --web opens
Mutations printed a list row, or nothing, so a person could not tell
what changed and scripts parsed rows meant for people. Commands now get
the helpers for the gh convention:

- done and warn print "✓ Closed issue alice/x#5 (Fix login)" and
  "! … is already closed" to stderr, only when stdout is a terminal and
  --json is not set.
- printURL prints a created resource's URL to stdout unless --json is
  set, so url=$(tea issue create …) works.
- batchDone applies a mutation to several selections like batch and
  reports each success with a done line instead of a table.
- issueRef formats "alice/x#5 (title)" references.

--web now prints "Opening localhost:3001/alice/project/issues/1 in your
browser." to stderr on a terminal before the browser starts.

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 1c9cc32d28 fix(admin): keep must_change_password unless it is passed to user edit
--must-change-password defaulted to true on admin user edit as well, so
changing a password with --password silently forced the user to change
it again at the next sign-in. On edit the flag now defaults to unset and
is sent only when passed; create keeps requiring a change by default.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 22:00:29 +02:00
bircni b6b3ddc91b fix: do not ask for confirmation right after prompting for input
secret set, variable set, team create, webhook create and admin user
create asked a confirmation that defaults to No right after the user
typed the missing values, so pressing Enter discarded the input. They
now submit what the user entered; deletions still confirm.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:59:18 +02:00
bircni fcfc497a77 fix(webhook): explain why webhook edit needs an authorization choice
Gitea 28 drops a webhook's stored authorization header on every edit
that does not send it again, and never returns it, so tea cannot leave
it unchanged. The error offered --clear-authorization as an equal
alternative, which wiped credentials users still needed. It now says
the header is lost unless sent again, asks for it first, and reserves
--clear-authorization for hooks without one.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:57:52 +02:00
bircni 784423bd9d fix(team): merge team edit --unit into the current unit policy
Gitea replaces a team's whole unit policy on edit, so
`tea team edit 5 --unit repo.wiki=write` revoked code, issue and pull
request access. team edit now merges --unit into the current policy;
--unit-replace keeps the old replacing behavior.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:56:32 +02:00
bircni a3f5468628 fix(team): name the team in delete prompts and protect the owner team
team delete confirmed with "delete team 3 on <host>" and deleted
whatever the ID pointed at, including an organization's owner team.
It now looks the team up first and asks to delete team "devs" (ID 5)
from acme. Deleting an owner team requires --force and, interactively,
typing the team's org/name.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:54:57 +02:00
bircni 872ba31607 fix(auth): prompt for --with-token without echo on a terminal
With stdin on a terminal, auth login --with-token read the pasted token
from the raw terminal, which echoed it in clear text without a prompt.
It now asks with a hidden "Paste an access token" prompt, and refuses
to read the terminal when prompting is disabled. Piped stdin is read as
before.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:53:35 +02:00
bircni 7b5c9ad9c6 fix(auth): reset other credential helpers in auth setup-git
setup-git appended tea's helper after the helpers Git already had, such
as osxkeychain from the system configuration. Those helpers answered
first, and after a push Git asked every helper to store tea's token, so
it ended up in the macOS Keychain where auth logout never removes it.
setup-git now writes an empty helper for the instance before tea's own,
as tea already does for its per-operation Git credentials. Add
--keep-helpers for the previous behavior.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:52:36 +02:00
bircni ab6228e25b fix: sanitize run logs and api responses written to a terminal
run logs and api copied server bytes straight to stdout. CI logs are
attacker-influenced through pull request workflows, so they could drive
the terminal of whoever read them. On a terminal, run logs now keep only
SGR color sequences, job names are reduced to plain text, and api drops
control characters from the response. Piped output and --output-file
stay byte-exact.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:50:36 +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 299428bf10 fix(pr): keep targeting the base repository after a fork PR checkout
Repository discovery preferred the current branch's upstream remote,
which for a fork pull request is the pulls/<owner>/<repo> remote that
pr checkout adds, so every later command targeted the fork. pr checkout
now records the pull request number and base remote in
branch.<name>.tea-pr and branch.<name>.tea-base-remote, discovery
honors the recorded base remote, and it skips pulls/* head remotes.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:47:06 +02:00
bircni 59bae60926 fix(repo): keep pushes from a cloned fork on the fork
Cloning a fork pointed the default branch at the upstream remote, so a
plain `git push` targeted the parent repository. The branch still pulls
from upstream, which keeps tea targeting the parent, but now sets
branch.<default>.pushRemote=origin so pushes go to the fork.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:44:44 +02:00
bircni b3dc3f47cb fix(repo): require an explicit repository to delete or archive with --yes
In a clone of a fork, repository discovery prefers the upstream remote,
so `tea repo delete --yes` deleted the parent repository. With --yes,
repo delete and repo archive now require the repository as an argument
or --repo. Interactively, repo delete asks the user to type the
repository name, and both prompts say where an inferred repository
came from.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:43:32 +02:00
bircni 4ea16c6510 refactor(app): split mutations.go into repo and pull mutation files
Move the repository mutation commands to repo_mutations.go and the pull request mutation commands to pull_mutations.go. No behavior change.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 21:35:00 +02:00
bircni f02245449b feat!: rebuild tea on Cobra with explicit noun/verb commands (#1126)
- **switch tea to the Cobra command tree:** `internal/app` wires the packages from parts 1–4 into explicit noun/verb commands with `--repo`, `--json` / `--jq`, `--sudo` and `--debug`; `main.go` points at it. Build, release and docs tooling (`tools/cli-docs`, Makefile, goreleaser, snap, CI) and README / CONTRIBUTING are updated.
- **remove the urfave/cli command tree:** deletes `cmd/`, `modules/`, the old integration tests, contrib completion scripts and the generated `docs/CLI.md`, and drops their dependencies. This commit only deletes.

## Breaking changes

This is a breaking release. It requires Gitea 28 or newer; configuration moves to separate `config.yml` and `hosts.yml` files and requires fresh authentication, and command names, aliases, flags and output formats change. `docs/migration-1.0.md` maps every old command, flag and environment variable to its replacement.

## Issues

fixes #1118
fixes #1116
fixes #1115
fixes #1094
fixes #1091
fixes #1073
fixes #1052
fixes #1032
fixes #1023
fixes #966
fixes #908
fixes #833
fixes #831
fixes #817
fixes #804
fixes #779
fixes #770
fixes #737
fixes #697
fixes #726
fixes #677
fixes #672
fixes #633
fixes #632
fixes #560
fixes #556
fixes #523
fixes #516
fixes #510
fixes #504
fixes #500
fixes #482
fixes #406
fixes #403
fixes #373
fixes #351
fixes #344
fixes #326
fixes #325
fixes #323
fixes #246
fixes #240
fixes #232
fixes #197
fixes #172
fixes #141
fixes #69
fixes #1110
fixes #569
fixes #480
fixes #666
fixes #670
fixes #457
fixes #643
fixes #498
fixes #490
fixes #1069
fixes #834
fixes #602
fixes #328
fixes #417
fixes #571
fixes #1029
fixes #489
fixes #1050
fixes #162
fixes #430
fixes #1087
fixes #683

Reviewed-on: https://gitea.com/gitea/tea/pulls/1126
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
2026-10-11 17:23:06 +00:00
bircni 12e415a7a7 refactor: add services layer on the Gitea Go SDK (#1125)
- `internal/services`: typed Gitea workflows built on `gitea.dev/sdk`, independent of CLI flags and terminal I/O: issues, pull requests, reviews, comments, labels, milestones, releases and assets, repositories, branches, wiki, Actions (workflows, runs, jobs, logs, artifacts, secrets, variables), organizations, teams, webhooks, notifications, SSH keys, tracked time, admin users, search and status.
- SDK types are mapped to tea's own output types, so `--json` stays a stable contract. Requests the SDK cannot express yet (e.g. pull list filters, multi-status run lists, org label archiving) go through small raw helpers, each marked `TODO(sdk)` with the missing SDK piece.
- Targets Gitea 28 or newer: no version or API-schema probing. List helpers fill pages capped by `MAX_RESPONSE_ITEMS`.

Nothing imports this package yet.

Reviewed-on: https://gitea.com/gitea/tea/pulls/1125
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
2026-10-11 17:15:41 +00: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
bircni 88b91faa54 refactor: add credential and Git packages (#1123)
- `internal/auth`: credentials in the OS keyring or a private file, OAuth login with token refresh, SSH HTTP-signature signers (key files with passphrase prompt, ssh-agent keys and certificates), and the Git credential helper protocol.
- `internal/git`: cancellable Git subprocesses, repository and remote discovery (tracked remote via `branch.<name>.remote`), PR checkout by full ref, AGit push, branch cleanup.

Nothing imports these packages yet.

Reviewed-on: https://gitea.com/gitea/tea/pulls/1123
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
2026-10-11 17:09:37 +00:00
bircni 2b218a5bae refactor: add settings, transport and alias packages (#1122)
Part 1/5 of the tea 1.0 rewrite.

**Stack** (split from #1121; each PR targets `main` and builds, tests and lints on its own — the shipped CLI only changes in part 5):

1. #1122 Toolchain and foundation packages ← this PR
2. #1123 Credentials and Git
3. #1124 Terminal
4. #1125 Services
5. #1126 CLI wiring, docs, removal of the old tree

## Changes

- **build:** require Go 1.27; bump gofumpt and golangci-lint to releases that can read Go 1.27 export data (own commit).
- `internal/clierr`: typed errors and executable exit codes.
- `internal/version`: build version and tags.
- `internal/filelock`: moved unchanged from `modules/filelock`.
- `internal/aliases`: user-defined command aliases.
- `internal/transport`: the HTTP client the Gitea SDK runs on (part 4): token, Sudo and SSH-signature auth, retries for safe reads, connect/TLS/response-header timeouts, typed API errors that keep Gitea's message, and a redacted request log for `--debug`; plus raw requests for `tea api`, signed downloads that follow object-storage redirects, and log streaming.
- `internal/settings`: split `config.yml` / `hosts.yml` with locked transactions, plus remote URL parsing.

Nothing imports these packages yet.

Note: `flake.nix` now asks for `go_1_27`; the pinned nixpkgs in `flake.lock` predates Go 1.27 and still needs a `nix flake update nixpkgs`.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1122
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
2026-10-11 17:06:38 +00:00
Lunny Xiaoandbircni bcd62a1fb2 fix(login): avoid SSH passphrase prompt with ssh-agent logins (#1102)
Fixes #866

## Problem

`tea` still prompts for an SSH key passphrase for logins created with
`--ssh-agent-key` or `--ssh-agent-principal`. During `tea login add`, tea
auto-discovers a matching private key in `~/.ssh` even when the login is
configured to use the running ssh-agent. That on-disk key is stored in
`ssh_key`, so later `Login.Client()` asks for its passphrase and tells the SDK
to load the file from disk instead of signing through the agent.

## Changes

- Stop auto-discovering a private key when the login uses the ssh-agent.
- Add `Login.SSHKeyPath()` and use it for HTTPSign and git auth, so agent
  logins pass an empty key path and the SDK talks to `ssh-agent`.
- Prefer SSH remotes for agent logins, matching key-file logins.
- Add unit tests for the new key-path and auto-discovery behavior.

## Testing

- `go test ./modules/config ./modules/task ./modules/git`
- `go build ./...`

---------

Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1102
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-10-01 19:58:41 +00:00
Lunny Xiao 6daaa7c05e Fix locale-dependent git repo detection (#1099)
Fixes #1089

## Root cause
`tea clone` initializes the command context by probing the current directory with `git rev-parse --show-toplevel`. `classifyRepoError` recognizes the "not a git repository" case by matching the English stderr text. When git emits localized errors (for example with `LANG=de_DE.UTF-8`), that match fails, and the otherwise harmless "no repo here" error is returned as fatal before cloning.

## Fix
Pin the locale of git subprocesses to `C` by overriding `LC_ALL`, `LANG`, and `LANGUAGE` in `runGitCommand`. This keeps all git output stable for parsing while leaving the parent process locale untouched.

## Tests
Added a regression test using a fake git executable that emits a German "not a repository" error unless the locale is forced to `C`, and verifies `ErrRepositoryNotExists` is returned.

Reviewed-on: https://gitea.com/gitea/tea/pulls/1099
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
2026-09-30 17:48:18 +00:00
ongolkandDanilo Sousa 9c12138d62 Make tea issues create honor --output json (#1114)
## Problem

Follow-up to #1111, covering the issues side of the same hole: `tea issues create` accepts `--output` (it parses through the urfave/cli v3 ancestor-flag cascade — `issues` carries the flag via `AllDefaultFlags`, `create` never declares it) but the action never reads it. Without this fix, `tea issues create --output json | jq .url` feeds jq a markdown document.

The default output is doubly hostile to consumers: glamour renders the details as markdown (with OSC 8 hyperlinks around the URL when piped), and a second bare `fmt.Println(issue.HTMLURL)` line follows it.

## What this changes

- `task.CreateIssue` now returns the created `*gitea.Issue` instead of printing it.
- `runIssuesCreate` switches on `--output`, mirroring the detail-command precedent and the merged create-PR behavior from #1111: `--output json` emits compact lean JSON; any other value (or no flag) falls through to the previous rendering, byte-identical to before.
- Lean JSON shape: `index`, `title`, `url`, `state` — matching `createdPullJSON` in `cmd/pulls/create.go`, including its post-review compact encoding.
- The interactive path is untouched — it only triggers when zero flags are set, so `--output` can never be active there.

Example:

```
$ tea issues create --output json --title "bug: thing" | jq -r .url
https://gitea.example.com/owner/repo/issues/42
```

There is no agit-flow equivalent on issues, so no extra guard is needed — unlike the pulls side, every creation path produces an `*gitea.Issue`.

---------

Co-authored-by: Danilo Sousa <code@danilosousa.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1114
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ongolk <238961+ongolk@noreply.gitea.com>
v0.16.0
2026-09-10 04:29:39 +00:00
Jan BaerandLunny Xiao 4d09587d4c fix(pulls): report the real reason when a merge is refused (#1107)
## Problem

`tea pr merge <index>` reports the same misleading error for every refusal:

```
failed to merge PR, is it still open?
```

The PR usually *is* still open — `tea pr <index>` shows it as open and lists `Conflicting files` — so the message sends users looking in the wrong direction.

## Root cause

Gitea answers an unmergeable PR with a 405 and a body naming the actual cause. The SDK's `MergePullRequest` is built on `getStatusCode`, which returns only the status code and never calls `statusCodeToErr`, so the body is discarded. tea receives `success=false, err=nil` with no server explanation to pass on, and fell back to guessing that the PR might be closed.

## Changes

- Derive the refusal reason from the pull request when a merge fails: already merged, closed, draft, or not mergeable.
- When the PR looks mergeable but was refused anyway, name the conditions tea cannot observe (required status checks, requested reviews, branch protection) instead of guessing.
- Include the PR index in the error.
- Add table-driven tests for every reason, plus the case where the follow-up PR lookup fails.

The extra API call happens only on the failure path.

Fixes #1022

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1107
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jan Baer <jan.s.baer@googlemail.com>
2026-09-09 19:15:46 +00:00