A table test covers the completions of alias, config, auth and notification commands, including notification IDs from the inbox. The integration test reads tea notification watch --json as one object per line, and notification feedback, watch lines and completions name a subject the same way.
Signed-off-by: bircni <bircni@icloud.com>
--verbose prints the method, URL without query and headers of each request, then the status and headers of its response, on stderr like curl -v. Credentials in Authorization, Proxy-Authorization and Cookie are hidden.
Signed-off-by: bircni <bircni@icloud.com>
tea version and --version printed "Version: development<TAB>golang:
1.27.2". They now print "tea version 1.0.0 (2026-10-01)", the release
notes URL for tagged releases, and "built with go1.27.2" with the build
tags. Builds from go install take the module version from the build
info, and the date comes from the commit the binary was built from.
Signed-off-by: bircni <bircni@icloud.com>
notification commands required a repository unless --mine was given.
They now act on the account's inbox (D17); -R, TEA_REPO or the new
--this-repo limit them to one repository, and --mine is a hidden no-op.
notification list is a table with ID, TYPE, REPOSITORY, SUBJECT ("#17
Improve greeting", bold when unread), STATE and UPDATED, a "Showing N of
M" line and an empty message on a terminal; piped rows add the status
and the URL. The ID links to the subject. JSON gains the subject number.
notification view shows the subject, its state and the latest comment,
then marks an unread notification as read. mark-read, mark-unread, pin
and unpin report each change ("Pinned notification 12
(alice/project#17)"), and --all confirms on a terminal and reports the
count. notification watch --json printed one array when it exited; it
now prints each change as one JSON object per line, prints a line per
change on a terminal, and runs until Ctrl-C there instead of stopping
after a minute.
Status and type filters complete and validate their values, notification
IDs complete from the inbox, and every command has examples.
Signed-off-by: bircni <bircni@icloud.com>
tea doctor printed the resolved instance, account and repository and
checked nothing; it failed with exit code 4 exactly when it was needed,
such as in a clone of an instance without an account, and with a
GITEA_TOKEN of another instance it blamed GITEA_INSTANCE_URL.
It now checks the settings files, whether the instance answers (version
and latency), whether the account's credential works and its token
scopes, whether the repository is readable (default branch), Git and
whether it asks tea for credentials, and where SSH remotes connect. Each
check shows a mark on a terminal and the command that fixes a problem;
piped, each is a tab-separated line. A GITEA_TOKEN for another instance
names both instances. doctor exits with 1 when a check fails, and --json
keeps the target fields and adds checks[].
Signed-off-by: bircni <bircni@icloud.com>
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>
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>
tea config get printed "key<TAB>value", so $(tea config get editor) was
not usable; it now prints the value alone, and --show-source appends the
source (D18). config get reads the instance's user, and instance settings
come from the selected instance (--host, TEA_HOST, default_host or the
sole instance). Unknown keys list the valid ones, and keys that need
--host or --user say so. config path no longer loads the settings, so it
works when hosts.yml is broken. config list is a table on a terminal.
config set confirms the change on a terminal and warns when default_host
has no account.
config.yml stores the hyperlinks (auto, always, never) and markdown
(auto, raw) preferences that tables and views now read, instead of the
TEA_HYPERLINKS and TEA_MARKDOWN variables. The environment table moves
out of tea config --help into tea help environment.
alias set refuses to replace an alias without --clobber, drops a leading
"tea", and explains how to quote an expansion with spaces or flags.
alias delete takes several names or --all, alias list is a table, and
an empty list says so on a terminal.
Signed-off-by: bircni <bircni@icloud.com>
secret set and variable set read the value from -b/--body, -F/--body-file
or standard input when it is not a terminal (one trailing line break
removed), and otherwise prompt for it; --env-file sets every NAME=value
of a dotenv file. --value and --value-file keep working. The scope flags
are -o/--org and --account on each command, and secret list no longer
offers --account, which Gitea cannot list.
Commands report what they did, naming the scope and the name Gitea
stores: "Set Actions secret LOWER_NAME for alice/x (names are
upper-cased)"; --json returns the stored name and omits an unknown
creation time. Deleting asks "Delete secret X from alice/x?". Lists show
NAME, CREATED and DESCRIPTION for secrets and NAME, VALUE and DESCRIPTION
for variables; variable view shows a record and the new variable get
prints the raw value. A missing secret or variable is reported by name
and scope.
The global --user selects the account tea acts as, unlike gh's --user,
so secret and variable add a note on a terminal pointing to --account
when it is given, as run list does for --actor. Help explains the scopes.
Signed-off-by: bircni <bircni@icloud.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
--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>
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>
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>
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>
--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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- **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#1118fixes#1116fixes#1115fixes#1094fixes#1091fixes#1073fixes#1052fixes#1032fixes#1023fixes#966fixes#908fixes#833fixes#831fixes#817fixes#804fixes#779fixes#770fixes#737fixes#697fixes#726fixes#677fixes#672fixes#633fixes#632fixes#560fixes#556fixes#523fixes#516fixes#510fixes#504fixes#500fixes#482fixes#406fixes#403fixes#373fixes#351fixes#344fixes#326fixes#325fixes#323fixes#246fixes#240fixes#232fixes#197fixes#172fixes#141fixes#69fixes#1110fixes#569fixes#480fixes#666fixes#670fixes#457fixes#643fixes#498fixes#490fixes#1069fixes#834fixes#602fixes#328fixes#417fixes#571fixes#1029fixes#489fixes#1050fixes#162fixes#430fixes#1087fixes#683
Reviewed-on: https://gitea.com/gitea/tea/pulls/1126
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
- `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>
- `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>
- `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>
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>
## Summary
- Extract duplicate \`getReleaseByTag\` into shared \`cmd/releases/utils.go\`
- Replace \`log.Fatal\` calls with proper error returns in config and login commands; \`GetLoginByToken\`/\`GetLoginsByHost\`/\`GetLoginByHost\` now return errors
- Remove dead \`portChan\` channel in \`modules/auth/oauth.go\`
- Fix YAML integer detection to use \`strconv.ParseInt\` (correctly handles negatives and large ints)
- Fix \`path.go\` error handling to use \`errors.As\` + \`syscall.ENOTDIR\` instead of string comparison
- Extract repeated credential helper key into local variable in \`SetupHelper\`
- Use existing \`isRemoteDeleted()\` in \`pull_clean.go\` instead of duplicating the logic
- Fix ~30 error message casing violations to follow Go conventions
- Use \`fmt.Errorf\` consistently instead of string concatenation in \`generic.go\`
Reviewed-on: https://gitea.com/gitea/tea/pulls/947
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-committed-by: Nicolas <bircni@icloud.com>