Commit Graph
177 Commits
Author SHA1 Message Date
bircni c1d51c386d fix(pr): refuse pr comment and pr attachment on issues
Gitea serves pull request comments and attachments through issue
endpoints, so tea pr comment create 1 posted on issue #1. pr comment and
pr attachment commands now check that the number is a pull request, and
comment IDs that the comment is on one, and point at the issue command
otherwise: "#1 in alice/project is an issue, not a pull request; use tea
issue comment create 1". Issues gain a kind field (issue or pull) in JSON.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 00:14:30 +02:00
bircni 883264512c fix(issue): reject unknown milestones in issue list and search filters
Gitea drops milestone filter values it cannot resolve and lists every
issue when none is left, so tea issue list --milestone nope printed all
issues and exited 0, and a pipeline closing the result would act on all
of them. issue list and search issue|pr -R now resolve each --milestone
value by ID or title and fail with "milestone "nope" not found in
alice/project". Instance-wide search cannot check the names and says so
on stderr.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 00:14:17 +02:00
bircni 81b0c1b99e fix: reconcile the pull request stream
Share one shortSHA helper, keep Markdown raw in view tests through the
markdown preference, and drop rawWindow now that pull request lists use
the paged helpers.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:56:25 +02:00
bircni 2c14ec2adf fix(status): end pr status with a single line break
pr status printed a blank line after its last section.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:55:13 +02:00
bircni 70295e521c test(pr): follow the pr changes in the Gitea lifecycle test and polish flag help
The disposable-Gitea lifecycle test expects pr checkout to name the
branch after the head branch and to fetch quietly, and ✓ feedback only
on terminals. The review flags name their file and commit values, and
pr edit gets a short Short with the editable fields in its Long text.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:55:13 +02:00
bircni 78f2ef5280 feat(status): show the current branch's pull request, mentions and runs from the clone
tea pr status starts with the pull request of the checked-out branch,
"[feat/one] - Checks: 2 pending · Review: Approved", then lists the
pull requests created by you, assigned to you and requesting your
review under bold headings, as linked #N or OWNER/REPO#N refs with
truncated titles instead of URL columns, and "Nothing here" for an
empty section. Its JSON adds current_branch.

tea status uses the same layout, adds a Mentions section (open issues
and pull requests that mention you; JSON "mentions"), and lists the
failing runs of the clone's repository without -R, naming the remote
it came from. Piped output keeps the URLs.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:55:13 +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 2c5e005bcf fix(pr): report checks like gh, exit 8 while they are pending
tea pr checks printed relative links, "Error: pull request checks have
not all succeeded" with exit 1 for checks that were only pending, and
nothing at all without checks. It now heads the table with a colored
verdict and "0 failing, 1 successful, and 2 pending checks", prints a
status icon and absolute links, and exits 8 while checks are pending
and 1 when one failed, without an Error line (D21). clierr.Quiet
carries such an exit code past main without a message. Without checks
it says "no checks reported for pull request alice/x#8".

--watch redraws the summary in place on a terminal, and an expired
--timeout reads "timed out after 3s waiting for 2 pending checks". The
pull request argument is optional, and --web opens the pull request.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:54:47 +02:00
bircni 7930f80865 feat(pr): list pull requests in a table and render issue and pr views as views
tea pr list prints a header, a "Showing N of M" line and the ID, TITLE,
BRANCH, STATE, AUTHOR and CREATED columns on a terminal, fit to its
width, and tab-separated rows when piped. Fork heads read
OWNER:BRANCH, drafts and merged pull requests have their own state,
and STATE appears on a terminal only when --state is not open. New
filters: --state merged, -d/--draft, -H/--head, -a/--assignee,
-S/--search and -w/--web, plus the gh shorthands -s, -A, -B, -l and
-m; --milestone takes a title or an ID, and @me works for --author and
--assignee. An empty list says so on stderr. draft, merged_at and
merged_by are exported in JSON, and reviewers, reviewer_teams and
merged_by, which Gitea's list leaves out, are read when --json names
them.

issue view and pr view render through terminal.View. Pull requests
show Draft and Merged badges, "alice merged into main from fix" with
the merge time, each reviewer's latest state ("bob (Approved), carol
(Requested)"), a checks summary pointing at tea pr checks, and why a
pull request cannot be merged. --comments includes reviews without a
body ("dave approved these changes") and comment IDs. Issues show
their due date and ref, and both kinds hint at --comments when there
are comments. pr view accepts #N, URLs and branch names, and its JSON
adds review_decision. Check links are absolute.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:54:47 +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 70443acce0 fix: reconcile the repository, platform and admin streams
Share one yesNo helper, drop the unused confirmTyped now that typed
confirmations go through confirmAction, keep Markdown raw in view tests
through the markdown preference, and print doctor latency in whole
milliseconds so fast instances do not show 0s.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:54:37 +02:00
bircni e093610ade feat(wiki): rename pages with --title alone, page lists on the client
wiki edit P --title New failed with "supply --body": renaming now keeps
the page's content, and edit needs one of --title, --body, --body-file
or --editor. wiki list --limit 1 returned every page because Gitea
replaces a limit of 1 with its default; the page list is now read whole
and paged on the client, so --limit and --page hold and the context line
counts the pages.

wiki create takes the title as an argument and, without --body, asks for
the content in the editor or an inline multi-line field. wiki view shows
the author, last update and revision count above the rendered page, and
--web opens it. wiki list and revisions show headed columns, page names
complete with Tab, and every change reports a ✓ line.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:52:33 +02:00
bircni 78b5ff3a19 feat(release): show notes and assets, upload with --clobber, download like gh
release view printed "v0.1.0 v0.1.0" and a URL: the notes and assets
were only in --json. It now shows the title and tag, a Latest,
Pre-release or Draft badge, who released it when and the target, the
assets with their sizes and the notes, rendered on a terminal. Without
a tag it shows the latest release, and --web opens it.

release asset upload silently attached a second asset with the same
name; it now refuses unless --clobber replaces the existing one.
release delete printed the deleted release as a live row; it reports
"✓ Deleted release v2.0.0 ... (tag kept; use --delete-tag)".

release list shows TITLE, TYPE, TAG and PUBLISHED. release create takes
asset files after the tag, -t/-n/-F/-e/-d/-p (--body and --body-file
still work) and the new alias. Downloads select assets by name or ID or
with --pattern, default to every asset of the latest release, write to
./NAME, --dir or --output with --clobber/--skip-existing, and --archive
fetches the source code. release upload and release download are
shortcuts for the asset commands, asset edit and delete take names or
IDs, and the release JSON gains author and assets.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:52:32 +02:00
bircni 02cf01093f feat(branch): branch from tags and commits, protect with explicit rules
branch create --source v1.0.0 (or a commit) failed with "The old branch
does not exist" because only old_branch_name was sent; the request now
sends old_ref_name, through a raw call until the SDK has the field.

branch protect nope exited 0 and created a rule for a missing branch,
and protecting twice failed with "permission denied". Branches must now
exist (--pattern protects a glob instead), an existing rule warns
"! main is already protected", and unprotecting an unprotected branch
warns instead of failing. The rule is explained in help and set with
--required-approvals, --allow-push, --status-check,
--block-on-rejected-reviews and --dismiss-stale-approvals.

branch list shows NAME (marking the default branch), COMMIT, PROTECTED,
UPDATED and MESSAGE, branch view is a full view with --web, branch
delete takes several branches, and every change reports a ✓ line.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:52:02 +02:00
bircni 682c9f66a2 feat(repo): validate create templates, publish local projects, verify edits
repo create --license NOPE returned Gitea's HTTP 500 internals. License,
.gitignore and label templates are now checked against the server's
lists, case-insensitively, and complete with Tab. --source DIR adds the
new repository as a remote of a local Git repository and --push pushes
its current branch; --public is the explicit opposite of --private, and
-d/-g/-l and the new alias follow gh. --trust-model now offers the names
Gitea accepts. A prompted name is confirmed with the visibility, owner
and host, and creation reports "✓ Created repository ..." and the URL.

repo edit --default-branch nope exited 0 without changing anything; the
branch must now exist. Edit gains --visibility, --enable-issues/-wiki/
-pull-requests/-projects, --allow-merge-commit/-squash/-rebase,
--delete-branch-on-merge, --default-merge-style and --add-topic/
--remove-topic, and reports which settings it changed.

repo migrate names the repository after the source URL, validates
--service before prompting, and reports the import with the URL.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:49 +02:00
bircni 368de47c40 feat(repo): sync forks on the server, wire forks into clones, validate set-default
repo sync OWNER/REPO, run where no Git remote points to the repository,
now updates it on the server: a fork's branch fast-forwards to its
parent's through merge-upstream, and a pull mirror fetches its source.

repo fork reports an existing fork ("! bob/x already exists") instead of
a raw HTTP 409, refuses to fork a repository into its own owner, and
gains --fork-name, --clone (Git flags after --) and --remote, which adds
the fork as origin and renames the old origin to upstream.

repo clone passes Git flags after --, and takes --upstream-remote-name
and --no-upstream for forks. Clones report what they set up.

repo set-default checks that the repository exists before saving it,
prints the saved repository with --view, removes it with --unset, and
reports the change.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:47 +02:00
bircni 7cd13ef64e feat(repo): add repo unarchive and repo rename, and report archive and delete
repo unarchive restores an archived repository, and repo rename renames
one, moving the Git remote or the saved default repository that selected
it to the new name. The archived-repository hint now points to
tea repo unarchive.

repo archive asks "Archive alice/x? It becomes read-only." and warns
"! alice/x is already archived" without a request when there is nothing
to do. Archive, unarchive, rename and delete confirm with a ✓ line on a
terminal, and --json returns the repository.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:45 +02:00
bircni eedbe9893d feat(repo): show repository columns, state and README
repo list and search repo printed bare names. They now render a table
with NAME, DESCRIPTION, INFO (public or private, fork, mirror, template,
archived, empty) and UPDATED under "Showing N of M repositories ...", an
empty-state message on stderr, and tab-separated rows with the URL when
piped. --type, --sort and --order complete and validate their values.

repo view shows the visibility and kind badges, stars, forks, open
issues and pull requests, the parent of a fork, the source of a mirror,
the default branch and the clone URLs, followed by the README of the
default branch (or --branch), rendered on a terminal. The repository
JSON gains empty, stars_count, forks_count, open_issues_count,
open_pr_count, topics and updated_at.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:45 +02:00
bircni 2330d174ea fix(repo): reject --repo on commands that act on no repository
tea repo list -R acme/service listed the account's repositories and
tea repo create foo -R alice/project created alice/foo: the flag was
silently ignored. repo list, repo create, repo migrate and search repo
now fail with a usage error that suggests the intended command, such as
"--repo is not used by repo list; did you mean: tea repo list acme".

repo list takes the owner as an argument (--owner still works), and
repo create accepts OWNER/NAME, creating in the organization OWNER.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:43 +02:00
bircni ea2e9aadbb fix(repo): sync a branch without switching to it
tea repo sync ran git switch on the synced branch, so syncing main while
on experiment left the user on main. A branch that is not checked out now
moves with update-ref, guarded by its old value; the checked-out branch
merges with --ff-only and needs a clean working tree; a missing branch is
created and set to track the remote. Diverged branches stay untouched.

Sync reports "✓ Synced main from origin/main (fast-forward a..b)" on a
terminal, "already up to date" when nothing changed, and the JSON result
gains the previous commit. Outside a clone, or without a remote for the
repository, the errors now name repo sync instead of checkout.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:43 +02:00
bircni 33f6406a1e refactor(auth): write piped auth status through writeTable
The tab-separated status rows no longer use the terminal.Row shim; the expiry is a time cell, so piped output keeps RFC3339 timestamps.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:50:47 +02:00
bircni 390e8e04f6 fix(notification): word --all confirmations and flag values clearly
notification mark-read --all asked "Marked as read all … ?"; each command now asks in its own words, such as "Pin all unread notifications in your inbox?". The status and type filters list their values in help, the api parameter flags read like gh's, and tea auth login --help aligns its method list.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:50:47 +02:00
bircni 9e74ec54ad test(notification): complete platform values and read watch output as NDJSON
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>
2026-10-11 23:50:46 +02:00
bircni 6c124fc998 feat(api): add --verbose to show the request and response headers
--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>
2026-10-11 23:50:46 +02:00
bircni 83dd4f7938 feat(version): print the version like gh version
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>
2026-10-11 23:50:46 +02:00
bircni 85034c524f fix(notification): read the inbox by default and stream watch as NDJSON
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>
2026-10-11 23:50:46 +02:00
bircni 014dba8512 fix(doctor): run every check instead of failing at the first problem
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>
2026-10-11 23:50:44 +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 5e8aa1011f fix(config): print only the value of config get and add display preferences
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>
2026-10-11 23:49:52 +02:00
bircni 0dfe7def9b feat(secret,variable): read values like gh and report stored names
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>
2026-10-11 23:46:57 +02:00
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