Upstream gitea/go-sdk#846 added deploy token support to the main
branch, so the fork-specific replace directive is no longer needed.
Co-authored-by: rossigee <ross@golder.org>
## 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>
## Problem
`tea pulls create` accepts `--output` (the flag parses successfully) but never reads it — the action always prints glamour-rendered markdown regardless of the requested format. That is a trap for anyone scripting the CLI: `tea pr create --output json | jq .url` quietly feeds jq a markdown document.
The current output is also hostile to URL-scraping consumers even without `--output`: glamour autolinks the bare PR URL into an OSC 8 terminal hyperlink, so piped stdout contains
```
\x1b]8;;https://host/owner/repo/pulls/33\x1b\\https://host/owner/repo/pulls/33\x1b]8;;\x1b\\
```
instead of a plain URL (repro: `tea pr create ... | cat -v`).
## Why the flag parses but does nothing
`create` itself does not declare `--output`: its flag set (`IssuePRCreateFlags`) carries no `OutputFlag`. The flag parses anyway because urfave/cli v3 resolves flags through `Command.lookupAppliedFlag`, which searches `appliedFlags` — "local flags for current command **or persistent flags from ancestors**". The parent `pulls` command carries `--output` via `AllDefaultFlags`, so the flag reaches the subcommand's parser while being absent from `create --help` — and was never consulted by the action.
## What this changes
- `task.CreatePull` now returns the created `*gitea.PullRequest` instead of printing it.
- `runPullsCreate` switches on `--output`, mirroring the existing detail-command precedent (`RunPullsDetails` in `cmd/pulls.go`): `--output json` emits a lean JSON object; any other value (or no flag at all) falls through to the previous `print.PullDetails` rendering, byte-identical to before.
- Lean JSON shape, since a freshly created PR has no reviews/comments/CI yet: `index`, `title`, `url`, `state`, `base`, `head`.
- `--agit` combined with `--output` now fails fast with an explicit error before any API call or `git push`: the agit flow creates the PR server-side via push and returns no object to print.
- The interactive path is untouched — it only triggers when zero flags are set, so `--output` can never be active there.
Example:
```
$ tea pr create --output json --title "fix: thing" | jq -r .url
https://gitea.example.com/owner/repo/pulls/33
```
---------
Co-authored-by: Danilo Sousa <code@danilosousa.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1111
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ongolk <238961+ongolk@noreply.gitea.com>
The deploy-tokens command was added but the auto-generated CLI documentation was not updated. Regenerated docs/CLI.md to include the new deploy-tokens, dt, deploy-token command with list, create, and delete subcommands.
- Add deploy-tokens subcommand with list, create, delete
- Create deploy token print helpers with token display (shown once only)
- Add unit tests for all commands
- Update SDK dependency to use fork with deploy token support
Relates to gitea/go-sdk#846
Adds a top-level 'tea deploy-keys' command under ENTITIES, providing
list, create, delete, and detail-by-id operations over the existing
Gitea REST API at /repos/{owner}/{repo}/keys.
- New CmdDeployKeys registered alongside tea webhooks/branches/etc.
- Subcommands: list (with pagination + --fingerprint filter),
create (<title> --key|--key-file [--read-only|--read-write]),
delete (<key-id> --confirm|-y), and a default <key-id> detail view.
- The Gitea API has no edit endpoint; 'change' is documented as
delete+create. No update subcommand is added.
- modules/print/deploy_key.go: DeployKeysList and DeployKeyDetails.
- Unit tests covering command metadata, flag wiring, key-material
resolution (--key vs --key-file, mutual exclusion, missing file),
delete confirmation logic, key-id parsing, and prompt formatting.
- README/CHANGELOG/CLI.md updated.
All four Gitea SDK methods used (CreateDeployKey, DeleteDeployKey,
GetDeployKey, ListDeployKeys) are already present in
code.gitea.io/sdk/gitea v0.23.2; no go.mod changes.
Refs #1066.
Signed-off-by: Ross Golder <ross@golder.org>
(cherry picked from commit 384540c91f9703d1e1b37dd6a8e51f502cc5f216)
Implements #1087.
Adds `tea login status [<login name>] [-o <format>]`, which verifies the stored token for one or all configured logins and reports:
- login name/URL and default status
- whether the token is valid (via `GET /api/v1/user`)
- auth method and token expiry
- whether the git credential helper is configured
Machine-readable output is available via the usual `-o` formats with fields `name`, `url`, `user`, `valid`, `auth_method`, `token_expiry`, `helper`, and `default`.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1105
Reviewed-by: bircni <bircni@icloud.com>
## Problem
`tea login add` can panic while auto-discovering SSH keys. The interactive login flow calls `regexp.FindStringSubmatch` and immediately indexes `[1]` without checking whether the regex matched. When the selected key display string does not have the expected format, the returned slice is `nil` and tea crashes with:
```
panic: runtime error: index out of range [1] with length 0
```
This is the crash reported in #527.
## Root cause
`regexp.Regexp.FindStringSubmatch` returns `nil` when the input does not match. Indexing that result with `[1]` assumes a match and causes the panic. The same unchecked pattern exists for SSH certificates and plain public keys in `modules/interact/login.go`.
## Changes
- Extract auto-discovered SSH key/certificate display parsing into `parseSSHPubkeySelection`.
- Add a `regexpSubmatch` helper that returns an error when a regex does not match, so login fails with a descriptive error instead of panicking.
- Add table-driven tests for local/agent keys, local/agent certificates, and malformed input.
Fixes#527
---------
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1100
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Closes#1095.
`tea issues create` and `tea pulls create` now resolve the description in the same way as comments: when stdin is piped and neither `--description` nor `--description-file` is given, the body is read from stdin. Both create and edit commands also accept:
```text
--description-file <path> # '-' reads stdin
```
This avoids the PowerShell 5.1 argument mangling and ANSI code page issues described in #1095.
## Changes
- Add `--description-file` to `issues create`, `issues edit`, `pulls create`, and `pulls edit`.
- Create commands fall back to piped stdin when no description flag is set.
- Add unit tests for the new body resolution.
---------
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1096
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Fixes `tea login add --oauth` hanging after the user authenticates in the
browser.
`xdg-open` (at least on Debian) runs the browser in the foreground, so it does
not exit until the browser does. `open.Run` waits for it, so tea is blocked and
doesn't get the oAuth callback from the browser.
This only happens when `xdg-open` has to start the browser. With one already
running, the new process hands off and exits immediately.
`open.Start` launches the opener and returns. The test mocks `xdg-open` with a
script that holds the foreground and fails if `openBrowser` waits on it.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1093
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: James Braid <jamesb@loreland.org>
The release pipeline uploaded artifacts to both AWS S3 (goreleaser `blobs:`) and Cloudflare R2 (custom publisher) during the migration period. The migration is done, so this removes the S3 half:
- drop the `blobs:` block from `.goreleaser.yaml`
- drop the `AWS_*` / `S3_*` env from the nightly and tag release workflows
- update the comments in `.goreleaser.yaml` and `scripts/upload-r2.sh` accordingly
Cloudflare R2 upload (including the early `--check-config` preflight step) is unchanged, and the `AWS_*` repo secrets are no longer used.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1092
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
The release pipeline uploaded artifacts to both AWS S3 (goreleaser `blobs:`) and Cloudflare R2 (custom publisher) during the migration period. The migration is done, so this removes the S3 half:
- drop the `blobs:` block from `.goreleaser.yaml`
- drop the `AWS_*` / `S3_*` env from the nightly and tag release workflows
- update the comments in `.goreleaser.yaml` and `scripts/upload-r2.sh` accordingly
Cloudflare R2 upload (including the early `--check-config` preflight step) is unchanged, and the `AWS_*` repo secrets are no longer used.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1092
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Aligns Go version handling with gitea, see https://github.com/go-gitea/gitea/pull/38559. `toolchain` names the build version, `go` stays the minimum.
The renovate extends match the other repos now. `security` is an empty preset, and `go-deps` only fast-tracked the toolchain bump into its own PR, which the weekly group carries instead.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1088
Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com>
Co-authored-by: silverwind <me@silverwind.io>
Fixes#1071
`TestRepoFromPath_Worktree` creates a throwaway temp repo and commits to it. On machines with `commit.gpgsign=true` in global git config, the commit fails with `No secret key`.
Override the global setting by setting `commit.gpgsign=false` in the temp repo's local config so the test is environment-independent.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1072
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Ross Golder <ross@golder.org>
Ports the Cloudflare R2 release mirror from [gitea.com/gitea/runner](https://gitea.com/gitea/runner) to `tea`, so release artifacts land in R2 alongside S3 for the duration of the migration away from S3.
Two commits, meant to be reviewed in order.
## 1. `ci(goreleaser): migrate release config to goreleaser v2`
A purely mechanical migration, no behaviour change intended:
- add `version: 2`
- `blobs.folder` -> `blobs.directory`
- `snapshot.name_template` -> `snapshot.version_template`
- `nightly.name_template` -> `nightly.version_template`
- `version: "~> v1"` -> `"~> v2"` in both release workflows
This is a prefactor rather than scope creep. Under goreleaser v1 the custom-publisher pipe runs *4th*, before the `release` pipe; under v2 it runs *last*. That ordering difference matters for the change below: on v1 a failed R2 upload would abort the publish after every artifact had already gone to S3 but **before** the Gitea release was created, leaving a half-finished release. On v2 the R2 mirror runs after the release exists, which matches the behaviour the runner repo already has in production.
The pre-existing `archives.format` deprecation warning is deliberately left alone; it is orthogonal to this change and the runner repo has not addressed it either.
## 2. `ci(goreleaser): mirror release artifacts to Cloudflare R2`
- **`scripts/upload-r2.sh`** — uploads one local file to one R2 object key using curl's built-in AWS SigV4 signer (R2 is S3-API compatible). Credentials are fed through `curl --config -` so they never appear in `ps` output. Also provides a `--check-config` preflight mode. This file is byte-identical to the runner repo's copy.
- **`.goreleaser.yaml`** — a `publishers:` entry mirroring the existing S3 `blobs:` upload into R2. A second `blobs:` entry is not usable here: the blob pipe authenticates from the global `AWS_*` environment and has no per-entry credentials, whereas `publishers:` supports per-entry `env:`.
- **Both release workflows** — forward the R2 secrets, plus an early `check R2 configuration` step. Custom publishers run as the very last step of goreleaser's publish pipeline, so without a preflight a missing secret would only surface after the release had been created and every artifact already uploaded to S3.
### Deviation from the runner implementation
The publisher here also sets `signature: true` in addition to `checksum: true`. `tea` has a `signs:` block that GPG-signs the checksum file, and the S3 blob pipe uploads the resulting `checksums.txt.sig`; without `signature: true` the R2 mirror would carry the artifacts and their checksums but no signature to verify them against.
The object key prefix is `tea/{{ .Version }}/...`, matching the existing S3 `directory: "tea/{{.Version}}"`.
## Required repository secrets
This PR is inert until these are configured. The preflight step will fail the release loudly if they are missing:
- `R2_ENDPOINT` — e.g. `https://<account>.r2.cloudflarestorage.com`
- `R2_BUCKET`
- `R2_ACCESS_KEY_ID`
- `R2_SECRET_ACCESS_KEY`
## Verification
- `goreleaser check` against the migrated config (with the pro-only `nightly:` block temporarily stripped, since the check ran with the OSS v2 binary): *configuration is valid*, the only deprecation being the pre-existing `archives.format`.
- A real `goreleaser build --snapshot --clean --single-target` against the v2 config: succeeded, including the `xz` and `.goreleaser.checksum.sh` post-hooks.
- `scripts/upload-r2.sh`: clean under `sh -n` and `shellcheck`; all four `--check-config` cases exercised (all vars unset, one missing, all set, wrong argument count).
- The actual upload path was not exercised end to end, since that needs live R2 credentials.
Reviewed-on: https://gitea.com/gitea/tea/pulls/1063
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
The prompt shown when no login matches the current repository reads:
```
NOTE: no gitea login detected, whether falling back to login 'X'?
```
Two problems, both raised by @magistra-aria in #817:
- **"whether"** is a conjunction that needs two stated alternatives, so it doesn't parse in front of a yes/no confirm.
- **"no gitea login detected"** is misleading. The condition is that no *configured login matched this repository's remote* — not that a Gitea instance is missing. Read literally it suggests the CLI expects gitea.com specifically, which is how at least one user (me) first misread it.
Reworded to say what actually happened, for both the interactive prompt and its non-interactive counterpart:
```
NOTE: no login matched this repository. Fall back to login 'X'?
```
Strings only, no logic change.
Refs #817
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zach Winter <contact@zachwinter.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1061
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zach Winter <222839+zachwinter@noreply.gitea.com>
Fixes#1055.
## Root cause
`tea notifications --mine` still initialized full repository context before checking the global notification scope, so it probed the current working directory with git and could select or fail on repository-derived context even though repository data is not needed.
## Changes
- Add an InitCommand option to skip local git repository discovery when a command does not need repository context.
- Use that option for notification list and mark-as operations when `--mine` is set.
- Add a regression test that makes `git` fail if invoked and verifies `notifications --mine` still uses the global notifications API.
## Tests
- `go test ./cmd/notifications ./modules/context`
Reviewed-on: https://gitea.com/gitea/tea/pulls/1056
The `Integration Test` job is currently failing repo-wide, on unrelated PRs and on `main`:
```
curl: (7) Failed to connect to gitea port 3000 after 7 ms
```
It fails at the health check, before any Go test runs. Recent examples: #1056 (7/18), #1059, #1060, #1061.
### Cause
The health check was a single bare curl fired as soon as `setup-go` finished — there was never a readiness wait. The job has been depending on gitea binding port 3000 within however long `checkout` + `setup-go` happened to take. Timings from the job logs, container start → curl:
| run | gap | result |
|---|---|---|
| #1021 (2026-06-21) | 7.4s | pass |
| #1056 (2026-07-18) | 6.3s | fail |
| #1060 (2026-07-25) | 6.3s | fail |
Two failures a week apart with an identical gap, and curl giving up in single-digit **milliseconds** with nothing listening, points at the service still starting rather than crashing.
### What I could not determine
Two things landed in the same window and I can't separate them from outside: the service image bump 1.26.2 → 1.27.0 (d664c01) and the hosted runner fleet going v1.0.3 → v2.0.0 (visible in the log header). The service container's own stdout isn't exposed by the job log endpoint, so **"still running migrations" is inferred from timing, not observed.** Happy to be corrected by anyone who can see the container logs.
### Why this fix regardless
The missing readiness wait is the actual bug class, independent of what shifted the timing. If the service is merely slower to boot, this fixes it permanently. If it is genuinely broken, this converts a cryptic 7ms connection refusal into an explicit 60s timeout with a clear error.
Locally I verified the YAML parses and exercised both loop paths in a shell (success exits immediately; exhaustion emits `::error::` and exits 1). I can't run Gitea Actions locally, so this workflow's first real execution is the CI run on this PR — a passing `Integration Test` here is the fix demonstrating itself.
---------
Co-authored-by: Zach Winter <contact@zachwinter.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1062
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zach Winter <222839+zachwinter@noreply.gitea.com>
Fixes https://gitea.com/gitea/tea/issues/1053
## What
tea hangs forever on Windows when it is started by something that owns a console but redirects
tea's stdio: a Windows service, a CI runner, an automation harness. Every command is affected,
`tea --version` included.
## Root cause
`modules/theme` imported `charm.land/lipgloss/v2/compat` for one struct type,
`compat.AdaptiveColor`. But compat detects the terminal background from package-level vars:
```go
var (
HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
Profile = colorprofile.Detect(os.Stdout, os.Environ())
)
```
Go initialises every package in the import graph before `main()` runs, and every file in package
`cmd` imports `modules/context`, which imports `modules/theme`. So tea queried the terminal on
every invocation, before urfave/cli had even looked at the arguments — which is why `--version`
hung.
On Windows that query opens `CONIN$`/`CONOUT$` and asks the console directly instead of giving up
when stdio is redirected, then waits for a reply that never comes. The read has a 2 second timeout,
but it does not fire, because the cancel it relies on is a no-op for that handle. Full trace in the
issue.
## Changes
- `modules/theme/theme.go` — drop the `compat` import and use `lipgloss.LightDark`, a plain
function that touches no terminal. `TeaTheme.Theme` is already handed the `isDark` it needs.
This also fixes a bug hiding in plain sight: `compat.AdaptiveColor` resolves against a
process-wide value detected at init, so the title color ignored the `isDark` argument huh passed
in. `Theme(true)` and `Theme(false)` returned the same color.
- `modules/theme/background.go` — new `HasDarkBackground()` helper that only asks the terminal when
stdin and stdout are both terminals, and otherwise assumes dark, which is the default lipgloss
itself falls back to. This is the same rule lipgloss already applies on Unix.
- `modules/interact/print.go` — `printTitleAndContent` called
`lipgloss.HasDarkBackground(os.Stdin, os.Stdout)` directly, so it hit the same wait on the
interactive paths, `tea login add` among them. It now goes through the helper.
## Why fix this in tea
The underlying bug is upstream and I have opened PRs for both halves of it: https://github.com/charmbracelet/ultraviolet/pull/138 and
https://github.com/charmbracelet/lipgloss/pull/713. But it is not fixed in any released version — lipgloss v2.0.5 is byte-identical to
v2.0.4 in the relevant files — so upgrading dependencies does not help, and tea would stay broken on
Windows until Charm cuts a release and tea bumps `go.mod`.
Separately, these changes stand on their own. tea should not query the terminal in order to print a
version string, whatever upstream does.
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Willem Kokke <mail@willem.net>
Closes#1042.
### What
`tea issue create`, `tea issue edit` and `tea pr create` all take the body via
`-d` / `--description`, but `tea comments add` and `tea comments edit` only
accepted it positionally — passing `-d` errored with
`flag provided but not defined: -d`.
This adds `-d` / `--description` to both `comments` subcommands so every
body-bearing command shares one flag.
### Behaviour / precedence
Unchanged for existing usage. Body resolution is now:
1. positional argument (kept first for back-compat),
2. `-d` / `--description`,
3. piped stdin,
4. `$EDITOR` (interactive only).
### Testing
- `go build`, `go vet ./...`, `go test -short ./cmd/... ./modules/...` all pass.
- Manually verified `tea comments add --help` / `edit --help` list the flag,
and that `-d` resolves the body (reaches the API, no parse error, no editor
prompt, no stdin block).
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1043
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Brien Coffield <coffbr01@gmail.com>
Co-committed-by: Brien Coffield <coffbr01@gmail.com>
When --redirect-url is omitted, the local callback listener binds to a
free port and opts.RedirectURL is rewritten with it. oauth2Config.RedirectURL
was never updated, so Exchange() sent the stale http://127.0.0.1:0 while
the authorize step had sent the real port. RFC-6749-compliant servers
(Gitea >= #37704, current Forgejo) reject the mismatch.
Propagate the resolved URL back into oauth2Config before Exchange. Add a
regression test using httptest that drives the flow end-to-end and asserts
the redirect_uri values match.
---------
Co-authored-by: dbankmann <204984+dbankmann@users.noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1019
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Daniel Bankmann <204984+dbankmann@noreply.gitea.com>
Co-committed-by: Daniel Bankmann <204984+dbankmann@noreply.gitea.com>
## Why
Today `tea comment` can only *add* a comment. Editing or deleting requires falling back to `tea api`. This came up while I was iterating on PRs in this same repo earlier today and had to correct a couple of comments by hand. Every comparable forge CLI (gh, glab, etc.) exposes these operations as first-class commands.
## What
Restructures `tea comment` from a single-action command into a parent with four subcommands. The parent's default action remains the existing "add" behavior, so the historical shorthand keeps working.
| Command | Purpose |
|---|---|
| `tea comment add <idx> [<body>]` | Add a comment (explicit subcommand) |
| `tea comment list <idx>` | Tabular listing including comment IDs |
| `tea comment edit <id> [<body>]` | Replace the body of one comment |
| `tea comment delete <id> [<id>...]` | Delete one or more comments |
| `tea comment <idx> [<body>]` | Unchanged — still routes to `add` |
The `list` command exists specifically so users can discover the IDs that `edit` and `delete` accept.
## Backward compatibility
The whole point of routing the parent's default `Action` through `add` is to preserve every existing invocation. `tea comment 1 "body"` still does what it did before. No flag or arg names change.
## Input forms (for add and edit)
Same pattern as the original `tea comment`:
1. Positional body (`tea comment edit <id> "new body"`) — wins if present.
2. Piped stdin if no positional body is given.
3. External `$EDITOR` (pre-populated with the current body, on `edit`) if neither.
This matches the stdin-handling fix in #1011 — positional body wins over a non-TTY stdin so the command doesn't hang in CI/subshells.
## Verification
All four subcommands were exercised live against `https://gitea.com/dinsmoor/tea-testing` issue #1. The test artifacts and a summary log are visible on that issue right now. Specifically:
- The annotated summary comment lists every operation tested and the comment IDs each one acted on.
- Comments 1197162 (legacy add), 1197163 (subcommand add, later edited), 1197164 (stdin add) are still there to be inspected.
- Comment 1197166 was created and then deleted; its absence from `tea comment list` output is evidence that delete works.
## New files
- `cmd/comments/add.go` — extracted from the old `cmd/comment.go`
- `cmd/comments/list.go`
- `cmd/comments/edit.go`
- `cmd/comments/delete.go`
- `modules/print/comment.go` — adds `CommentsList` helper for the tabular output
`cmd/comment.go` is rewritten as a thin parent that wires these together.
## Open questions for the reviewer
- **Naming**: should the top-level command be `comments` (plural) or stay `comment` (singular)? I kept it singular with `comments` as an alias to match the existing user-visible name.
- **Delete confirmation**: I did not add a confirmation prompt — `delete` just deletes. Some projects gate this behind `--yes` / interactive `[y/N]`. I'd rather follow whatever convention the maintainers prefer.
- **Output format on list**: currently uses the existing `print.tableWithHeader` helper, matching `tea organizations list` etc. Other tea listings support `--output json` / `--output csv` via the shared `--output` flag, which works here automatically through the same helper.
---
This patch was authored interactively with an AI assistant, driven and reviewed by a human (Tyler / @dinsmoor) every step.
*pull request created by Tyler's lovingly wrangled demon machine <3*
Reviewed-on: https://gitea.com/gitea/tea/pulls/1015
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: dinsmoor <204368+dinsmoor@noreply.gitea.com>
Co-committed-by: dinsmoor <204368+dinsmoor@noreply.gitea.com>
Closes#979. An alternative to the approach in #980 — this one is purely client-side title-mangling, no SDK changes needed.
Gitea already treats any PR with a `WIP:` or `[WIP]` title prefix (case-insensitive) as a draft. This patch wires three flags around that behavior:
- `tea pulls create --draft` — prepend `WIP: ` to the title at creation time
- `tea pulls edit --draft <idx>` — add `WIP: ` to an existing PR's title
- `tea pulls edit --ready <idx>` — strip any recognized draft prefix
All three are idempotent. `--draft` and `--ready` on edit are mutually exclusive. If the user also passes `--title` on edit, the toggle applies to the supplied title; otherwise the current title is fetched from the server first.
Why this approach over a server-payload-based one: Gitea's draft state is *defined* as the title-prefix convention (see the Gitea source for `HasWIPPrefix`). Modeling it server-side would either duplicate or fight that. A small string helper covers it without needing the SDK to add a `Draft` field.
Verified against `gitea.com` (1.26.0+dev) with a throwaway repo:
- create with `--draft` → server reports `draft: true` ✓
- `edit --ready` strips → `draft: false` ✓
- `edit --draft` adds back → `draft: true` ✓
- second `edit --draft` is idempotent ✓
- `edit --draft --ready` errors ✓
Unit tests for the prefix detection live in `modules/utils/draft_test.go`.
---
This patch was authored interactively with an AI assistant, driven and reviewed by a human (Tyler / @dinsmoor) every step. Reproduction, design decisions, and the choice not to follow #980's payload approach were mine — happy to discuss any of it.
*pull request created by Tyler's lovingly wrangled demon machine <3*
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1008
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Tyler <tyler@dinsmoor.us>
Co-committed-by: Tyler <tyler@dinsmoor.us>