Add documentation for gt, the Gitea CLI (#603)

Adds a CLI product for gt (https://gitea.com/gitea/cli), set up like the runner, with short guides for installation, logging in, CI and scripting. Each command's details stay in gt <command> --help. Until the first release, the docs are served at /cli/; make cut-version PRODUCT=cli VERSION=0.1 adds the release series.

Closes https://gitea.com/gitea/cli/issues/16

<!-- cloudflare-preview --> Preview: https://pr-603.docs-gitea-com.pages.dev

---------

Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/603
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: silverwind <[email protected]>
This commit is contained in:
bircni
2026-10-23 12:16:49 +00:00
co-authored by silverwind
parent 9699ec2c87
commit 09bb4cabcf
20 changed files with 315 additions and 45 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ update-runner-docs:
update-runner-docs-released:
./update_runner_docs.sh --released
# freezes the current docs or runner tree as a new version, see
# freezes the current docs, runner or cli tree as a new version, see
# scripts/cut-version.mjs
.PHONY: cut-version
cut-version:
+13 -1
View File
@@ -3,13 +3,14 @@
The sources of [docs.gitea.com](https://docs.gitea.com), built with
[Astro](https://astro.build) and [Starlight](https://starlight.astro.build).
The site covers three products, all served from this repository:
The site covers four products, all served from this repository:
| Product | Content | Versions | Languages |
| --- | --- | --- | --- |
| Docs | `docs/`, `versioned_docs/`, `i18n/` | next, 28, 1.27 … 1.22 | English, 简体中文, 繁體中文 |
| API | `static/swagger-*.json`, `static/openapi3-*.json` | next, 28, 1.27 … 1.22 | English |
| Runner | `runner-docs/`, `runner-docs_versioned_docs/` | develop, 5 … 0 | English |
| CLI | `cli-docs/`, `cli-docs_versioned_docs/` | develop | English |
The enterprise documentation is built and deployed from its own repository and
reached through `/enterprise/`, which a Cloudflare worker maintained elsewhere
@@ -53,6 +54,7 @@ group from the `_category_.json` of its directory.
```shell
make cut-version PRODUCT=docs VERSION=1.28
make cut-version PRODUCT=runner VERSION=6
make cut-version PRODUCT=cli VERSION=0.1
```
This freezes the current tree, its translations and its sidebar. The label of
@@ -115,6 +117,16 @@ workflow runs weekly and opens a pull request whenever the runner's CLI or
example configuration changed. Generating them needs Go, since the script builds
the runner binary.
## CLI docs
The documentation of `gt`, the Gitea CLI from
[gitea/cli](https://gitea.com/gitea/cli), is laid out like the runner docs, with
its sidebar in `cli-sidebars.js`. No series is documented yet, so `cli-docs/` is
served at `/cli/`. Once a release exists, cut its series and list it in
`cliSeries` in `packages/content-loader/src/products.ts`, which moves the develop
docs to `/cli/develop/`. The guides leave the flags of each command to
`gt <command> --help`.
## Deployment
`main` is built and published by the `Build and Publish Docs site` workflow, to
+55
View File
@@ -0,0 +1,55 @@
---
sidebar_position: 3
---
# Logging in
On a terminal, `gt auth login` asks for the instance and how to log in. gt never asks for
your password.
## OAuth in a browser
```bash
gt auth login --host https://gitea.example.com --oauth
```
gt opens the instance's authorization page, so single sign-on and two-factor
authentication work as on the web. The browser has to run on the same machine as gt.
`--scopes` limits the token to fewer [scopes](/development/oauth2-provider#scopes); gt
refreshes OAuth tokens on its own.
## Access token
Create a token under **Settings > Applications** on the instance and pass it on stdin:
```bash
gt auth login --host https://gitea.example.com --with-token < token.txt
```
The token needs `read:user`, and the scopes of what you do with gt, such as
`write:repository` and `write:issue` for working on pull requests.
## SSH key
gt can sign its requests with an SSH key registered for your account instead of using a
token:
```bash
gt auth login --host https://gitea.example.com --ssh-key ~/.ssh/id_ed25519
```
## Accounts
Tokens go to the system keyring, or with `--credential-store file` to gt's configuration
directory on machines without one. Run `gt auth login` once per instance and account.
gt picks the instance from the repository's remote or `--host`, and the account with
`gt auth switch` or `--user`. `gt auth status` lists your accounts and `gt auth logout`
removes one.
For instances behind an access proxy, `--header` and `--client-cert` add headers and a
client certificate.
## Git over HTTPS
`gt auth setup-git` lets `git push` and `git fetch` over HTTPS use gt's login. gt's own
clone and checkout commands need no setup.
+40
View File
@@ -0,0 +1,40 @@
---
sidebar_position: 4
---
# Using gt in CI
Give gt a token and an instance through the environment, and turn prompts off:
| Variable | Description |
| --- | --- |
| `GITEA_TOKEN` | Access token; gt never stores it |
| `GITEA_CLI_HOST` or `GITEA_INSTANCE_URL` | URL of the instance |
| `GITEA_CLI_REPO` | Repository, like `--repo` |
`--no-input` makes a command fail instead of prompting, and `--yes` confirms changes in
advance.
## Gitea Actions
Gitea does not allow secret names that start with `GITEA_`, so store the token as, for
example, `GT_TOKEN`:
```yaml
on:
push:
branches: [main]
jobs:
staging:
runs-on: ubuntu-latest
env:
GITEA_TOKEN: ${{ secrets.GT_TOKEN }}
GITEA_CLI_HOST: ${{ gitea.server_url }}
GITEA_CLI_REPO: ${{ gitea.repository }}
steps:
- run: curl -fsSL https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh | sh
- run: gt workflow run deploy.yml --ref main --field env=staging --no-input
```
`gt run watch` and `gt pr checks --watch` wait for runs and checks and fail when one fails.
+38
View File
@@ -0,0 +1,38 @@
---
sidebar_position: 1
slug: /
---
# Gitea CLI
[gt](https://gitea.com/gitea/cli) is the official command line client for Gitea. It works
with repositories, issues, pull requests, releases, Actions and the other resources of a
Gitea instance, from a terminal or a script.
:::info Development version
These pages describe builds from the `main` branch, published as `nightly` binaries and images.
:::
gt needs Gitea 28 or later, and Git for the commands that clone, check out or push.
## Getting started
1. [Install gt](installation.md).
2. [Log in](authentication.md):
```bash
gt auth login
```
3. Run commands in a clone of a repository on that instance, or select one with `--repo`:
```bash
gt issue list --label bug
gt pr checkout 13
gt issue view 42 --repo gitea.com/gitea/cli
```
`gt doctor` checks your setup and suggests a fix for each problem it finds.
`gt <command> --help` lists the flags of a command, and `gt help environment` the global
flags and environment variables.
+37
View File
@@ -0,0 +1,37 @@
---
sidebar_position: 2
---
# Installation
gt is a single binary for Linux, macOS, Windows and FreeBSD.
## Install script
The install script downloads gt from [dl.gitea.com](https://dl.gitea.com/gitea-cli/),
verifies its checksum and installs it.
```bash
# Linux, macOS, FreeBSD
curl -fsSL https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh | sh
```
```powershell
# Windows
irm https://gitea.com/gitea/cli/raw/branch/main/scripts/install.ps1 | iex
```
Set `GITEA_CLI_VERSION` to install a specific version, or `nightly` for the latest build
of `main`.
## Other ways
- **Binaries:** download them from [dl.gitea.com/gitea-cli](https://dl.gitea.com/gitea-cli/).
- **Docker:** the [`gitea/gitea-cli`](https://hub.docker.com/r/gitea/gitea-cli) image runs
`gt` as its entry point.
- **Go:** `go install gitea.dev/cli/cmd/gt@latest`
## Shell completion and man pages
`gt completion <shell>` prints the completion script for bash, zsh, fish or PowerShell;
`gt completion <shell> --help` shows how to load it. `gt man` prints the manual.
+22
View File
@@ -0,0 +1,22 @@
---
sidebar_position: 5
---
# Output and scripting
Scripts should read JSON: the text output is meant for people and may change.
```bash
gt pr view 123 --json number,title,state
gt issue list --json number,title --jq '.[] | "\(.number)\t\(.title)"'
```
`--json` prints all fields or the ones you list; the **JSON FIELDS** section of a command's
`--help` names them. `--jq` filters the result without needing jq installed. Piped lists
print as tab-separated values.
gt exits with 0 on success, 1 when a command fails, 2 on a usage error and 4 when
authentication is missing or rejected; `gt help exit-codes` lists all codes.
`gt api` calls any API endpoint with gt's login, and `gt alias set` defines shortcuts for
commands you use often.
+1
View File
@@ -0,0 +1 @@
[]
+16
View File
@@ -0,0 +1,16 @@
// Sidebar of the gt (Gitea CLI) documentation (develop version).
// Released versions carry their own copy in
// cli-docs_versioned_sidebars/version-<version>-sidebars.json.
module.exports = {
cli: [
{
type: 'doc',
id: 'index',
label: 'What is gt?',
},
'installation',
'authentication',
'ci',
'scripting',
],
};
+4 -2
View File
@@ -1,11 +1,13 @@
# Redirects for legacy URLs and routes the site no longer serves directly.
# The api reference and the runner docs are separate products, published in
# english only, so they have no localized copy.
# The api reference, the runner and the cli docs are separate products,
# published in english only, so they have no localized copy.
/zh-cn/api/* /api/:splat 301
/zh-tw/api/* /api/:splat 301
/zh-cn/runner/* /runner/:splat 301
/zh-tw/runner/* /runner/:splat 301
/zh-cn/cli/* /cli/:splat 301
/zh-tw/cli/* /cli/:splat 301
# The version served at the root of a product is also reachable under its
# number, which is what the release notes and old links use.
+3 -1
View File
@@ -7,6 +7,7 @@
"https://docs.gitea.com/zh-cn/",
"https://docs.gitea.com/zh-tw/",
"https://docs.gitea.com/runner/",
"https://docs.gitea.com/cli/",
"https://docs.gitea.com/api/",
"https://docs.gitea.com/enterprise/"
],
@@ -15,7 +16,8 @@
"https://docs.gitea.com/1.2*/**",
"https://docs.gitea.com/*/1.2*/**",
"https://docs.gitea.com/api/1.2*/**",
"https://docs.gitea.com/runner/[0-9]/**"
"https://docs.gitea.com/runner/[0-9]/**",
"https://docs.gitea.com/cli/[0-9]*/**"
],
"selectors": {
"lvl0": { "selector": "//nav[contains(@aria-labelledby,'starlight__sidebar')]//a[@aria-current='page']/ancestor::details//summary//span", "type": "xpath", "global": true, "default_value": "Documentation" },
+42 -2
View File
@@ -8,7 +8,7 @@
export type LocaleId = 'en-us' | 'zh-cn' | 'zh-tw';
export type ProductId = 'docs' | 'api' | 'runner' | 'enterprise';
export type ProductId = 'docs' | 'api' | 'runner' | 'cli' | 'enterprise';
/** Values substituted for `@name@` placeholders in the markdown sources. */
export interface VersionVariables {
@@ -246,6 +246,40 @@ const runnerProduct: ProductDef = {
],
};
/**
* gt, the Gitea CLI: laid out like the runner. Until the first series is listed
* here the develop docs are served at the product root, without the unreleased
* banner, since there is no release to point to. Listing a series moves them to
* `/cli/develop/`.
*/
const cliSeries: { id: string; latestRelease: string }[] = [];
const cliProduct: ProductDef = {
id: 'cli',
label: 'CLI',
base: 'cli',
locales: ['en-us'],
versions: [
{
id: 'develop',
label: 'develop',
sources: { 'en-us': 'cli-docs' },
...(cliSeries.length > 0
? { path: 'develop', banner: 'unreleased' as const }
: { path: '', latest: true }),
},
...cliSeries.map(
({ id, latestRelease }, index): VersionDef => ({
id,
label: latestRelease,
path: index === 0 ? '' : id,
latest: index === 0,
sources: { 'en-us': `cli-docs_versioned_docs/version-${id}` },
}),
),
],
};
/** API reference, rendered from the swagger documents by starlight-openapi. */
const apiProduct: ProductDef = {
id: 'api',
@@ -301,7 +335,13 @@ const enterpriseProduct: ProductDef = {
externalBaseUrl: 'https://docs.gitea.com/enterprise',
};
export const products: ProductDef[] = [docsProduct, apiProduct, runnerProduct, enterpriseProduct];
export const products: ProductDef[] = [
docsProduct,
apiProduct,
runnerProduct,
cliProduct,
enterpriseProduct,
];
export function getProduct(id: ProductId): ProductDef {
const product = products.find((candidate) => candidate.id === id);
+3 -3
View File
@@ -1,7 +1,7 @@
/**
* Builds one sidebar per (product, version, language) out of the loaded pages,
* deriving it from `_category_.json` and from the
* handwritten sidebar files of the runner docs.
* handwritten sidebar files of the runner and the cli docs.
*/
import { defaultLocale } from './products';
@@ -44,7 +44,7 @@ export interface SidebarGroup {
export type SidebarEntry = SidebarLink | SidebarGroup;
/** A hand written sidebar file, as used by the runner docs. */
/** A hand written sidebar file, as used by the runner and the cli docs. */
export type SidebarSourceItem =
| string
| { type: 'doc'; id: string; label?: string }
@@ -148,7 +148,7 @@ function treeOf(pages: SidebarPage[]): Tree {
return { build, label };
}
/** Sidebar described by a hand written sidebar file, used by the runner docs. */
/** Sidebar described by a hand written sidebar file, used by the runner and the cli docs. */
function fromSidebarSource(
items: SidebarSourceItem[],
pages: SidebarPage[],
+10 -8
View File
@@ -4,10 +4,11 @@
*
* node scripts/cut-version.mjs docs 1.28 # freezes docs/ as 1.28
* node scripts/cut-version.mjs runner 4 # freezes runner-docs/ as 4
* node scripts/cut-version.mjs cli 0.1 # freezes cli-docs/ as 0.1
*
* For the docs it copies the english tree, every translation and the sidebar
* file; for the runner the english tree and its sidebar. The version list
* (`versions.json`, `runner-docs_versions.json`) is updated too.
* file; for the runner and the cli the english tree and its sidebar. The
* version list (`versions.json`, `<product>-docs_versions.json`) is updated too.
*
* The label, the release variables (`@version@`, `@dockerVersion@`, ...) and
* which version is served at the root live in
@@ -21,8 +22,8 @@ import process from 'node:process';
const [product, version] = process.argv.slice(2);
if (!product || !version || !['docs', 'runner'].includes(product)) {
console.error('usage: node scripts/cut-version.mjs <docs|runner> <version>');
if (!product || !version || !['docs', 'runner', 'cli'].includes(product)) {
console.error('usage: node scripts/cut-version.mjs <docs|runner|cli> <version>');
process.exit(1);
}
@@ -52,6 +53,7 @@ async function freeze(from, to) {
async function freezeSidebar(from, to) {
if (await exists(to)) throw new Error(`${to} already exists`);
const sidebar = require(path.join(root, from));
await mkdir(path.join(root, path.dirname(to)), { recursive: true });
await writeFile(path.join(root, to), `${JSON.stringify(sidebar, null, 2)}\n`);
console.log(` ${from} -> ${to}`);
}
@@ -80,12 +82,12 @@ if (product === 'docs') {
await addToVersions('versions.json');
} else {
await freeze('runner-docs', `runner-docs_versioned_docs/version-${version}`);
await freeze(`${product}-docs`, `${product}-docs_versioned_docs/version-${version}`);
await freezeSidebar(
'runner-sidebars.js',
`runner-docs_versioned_sidebars/version-${version}-sidebars.json`,
`${product}-sidebars.js`,
`${product}-docs_versioned_sidebars/version-${version}-sidebars.json`,
);
await addToVersions('runner-docs_versions.json');
await addToVersions(`${product}-docs_versions.json`);
}
console.log(
+15 -12
View File
@@ -1,14 +1,15 @@
# Astro + Starlight site
Renders the gitea documentation with [Starlight](https://starlight.astro.build):
the docs (6 versions x 3 languages), the runner docs (4 series) and the api
reference (7 swagger documents). The enterprise docs stay in their own
deployment and are linked from the header.
the docs (6 versions x 3 languages), the runner docs (4 series), the cli docs
and the api reference (7 swagger documents). The enterprise docs stay in their
own deployment and are linked from the header.
The content directories are the ones the site has always used — `docs/`,
`versioned_docs/`, `i18n/`, `runner-docs/`, `runner-docs_versioned_docs/` and
`static/swagger-*.json` (plus `static/openapi3-*.json` since gitea 1.27) — so
the release scripts and the translation workflow stay unchanged.
the release scripts and the translation workflow stay unchanged. The cli docs
in `cli-docs/` follow the layout of the runner docs.
## Running it
@@ -40,6 +41,7 @@ pickers, the version banner, the search facets and the api schemas.
| `i18n/zh-cn/docs/version-1.27/` | `/zh-cn/` |
| `runner-docs_versioned_docs/version-3/` | `/runner/` |
| `runner-docs/` | `/runner/develop/` |
| `cli-docs/` | `/cli/`, until a cli series is released |
| `static/openapi3-27.json` | `/api/` |
| `static/openapi3-latest.json` | `/api/next/` |
| `static/swagger-26.json` | `/api/1.26/` |
@@ -60,8 +62,9 @@ of every file, the second normalizes and renders it:
Sidebars are built per (product, version, language) from `_category_.json` and
from the sidebar files (`sidebars.js`, `versioned_sidebars/`,
`runner-sidebars.js`, `runner-docs_versioned_sidebars/`), and selected in
`src/routeData.ts`, since starlight only supports a single static sidebar.
`runner-sidebars.js`, `runner-docs_versioned_sidebars/`, `cli-sidebars.js`),
and selected in `src/routeData.ts`, since starlight only supports a single
static sidebar.
Set `GITEA_DOCS_STRICT_LINKS=true` to fail the build on a broken internal link
instead of warning.
@@ -69,12 +72,12 @@ instead of warning.
## Products and languages
The docs are the site itself, so their language comes first: `/zh-cn/1.26/`.
The runner and the api are separate products and keep their language inside the
product, `/runner/zh-cn/3/`, which is reserved: both are published in english
only today. Starlight builds a fallback page in every configured language for
every english page, so `/zh-cn/runner/` would exist; the post build integration
removes those directories and `cloudflare/_redirects` sends them to the
english page.
The runner, the cli and the api are separate products and keep their language
inside the product, `/runner/zh-cn/3/`, which is reserved: all are published in
english only today. Starlight builds a fallback page in every configured
language for every english page, so `/zh-cn/runner/` would exist; the post build
integration removes those directories and `cloudflare/_redirects` sends them to
the english page.
The version served at the root of a product has no number in its url: docs 1.27
is `/`, runner 3 is `/runner/` and api 1.27 is `/api/`. `/1.27/`, `/runner/3/`
+3 -3
View File
@@ -23,7 +23,7 @@ type Strings = {
const en: Strings = {
versionLabel: 'Version',
languageLabel: 'Language',
products: { docs: 'Docs', api: 'API', runner: 'Runner', enterprise: 'Enterprise' },
products: { docs: 'Docs', api: 'API', runner: 'Runner', cli: 'CLI', enterprise: 'Enterprise' },
footer: { Community: 'Community', Code: 'Code', More: 'More' },
signIn: 'Sign In',
translationNotice: 'This translation may be behind the english original.',
@@ -41,7 +41,7 @@ const strings: Record<string, Strings> = {
'zh-cn': {
versionLabel: '版本',
languageLabel: '语言',
products: { docs: '文档', api: 'API', runner: 'Runner', enterprise: '企业版' },
products: { docs: '文档', api: 'API', runner: 'Runner', cli: 'CLI', enterprise: '企业版' },
footer: { Community: '社区', Code: '开源代码', More: '更多' },
signIn: '登录',
translationNotice: '当前中文文档翻译不是最新版,访问英文版本查看最新内容,或',
@@ -56,7 +56,7 @@ const strings: Record<string, Strings> = {
'zh-tw': {
versionLabel: '版本',
languageLabel: '語言',
products: { docs: '文件', api: 'API', runner: 'Runner', enterprise: '企業版' },
products: { docs: '文件', api: 'API', runner: 'Runner', cli: 'CLI', enterprise: '企業版' },
footer: { Community: '社區', Code: '開源程式碼', More: '更多' },
signIn: '登入',
translationNotice: '當前中文文檔翻譯不是最新版,訪問英文版本查看最新內容,或',
+3 -3
View File
@@ -11,9 +11,9 @@ import { defaultLocale, locales, products, type LocaleId } from '@gitea-docs/con
* generated as `/api/126/`. The directories are renamed and the links inside
* the build are rewritten.
* - starlight builds a fallback page in every configured language for every
* english page. That is what we want for the docs, but the runner and the api
* are separate products with their own language list, so `/zh-cn/runner/` has
* to go; `cloudflare/_redirects` sends it to the english page.
* english page. That is what we want for the docs, but the other products
* have their own language list, so `/zh-cn/runner/` has to go;
* `cloudflare/_redirects` sends it to the english page.
*/
export function giteaPostBuild(): AstroIntegration {
return {
+1 -1
View File
@@ -90,7 +90,7 @@ function addScopeToggle(root: HTMLElement, onChange: (allVersions: boolean) => v
/**
* While all versions are searched the same page shows up once per version, so
* the version (and the product, for the api and the runner) is appended to the
* the version (and the product, for every product but the docs) is appended to the
* title of a result.
*/
function labelResult(result: PagefindResult): void {
+7 -7
View File
@@ -12,9 +12,9 @@ import { getCollection } from 'astro:content';
import { repoRoot } from '../config/paths';
/**
* Sidebar files: the top level order of the docs and the
* handwritten sidebars of the runner, one per version. Read as is, so cutting a
* version keeps needing no change here.
* Sidebar files: the top level order of the docs and the handwritten sidebars
* of the runner and the cli, one per version. Read as is, so cutting a version
* keeps needing no change here.
*/
function sidebarSources(): Map<string, SidebarSourceItem[]> {
const sidebars = new Map<string, SidebarSourceItem[]>();
@@ -42,11 +42,11 @@ function sidebarSources(): Map<string, SidebarSourceItem[]> {
version.id === 'next'
? 'sidebars.js'
: `versioned_sidebars/version-${version.id}-sidebars.json`;
} else if (product.id === 'runner') {
files[`runner@${version.id}`] =
} else if (product.id === 'runner' || product.id === 'cli') {
files[`${product.id}@${version.id}`] =
version.id === 'develop'
? 'runner-sidebars.js'
: `runner-docs_versioned_sidebars/version-${version.id}-sidebars.json`;
? `${product.id}-sidebars.js`
: `${product.id}-docs_versioned_sidebars/version-${version.id}-sidebars.json`;
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { z } from 'astro:content';
* per version sidebars, the version and language pickers and the search facets.
*/
export const giteaMeta = z.object({
product: z.enum(['docs', 'api', 'runner', 'enterprise']),
product: z.enum(['docs', 'api', 'runner', 'cli', 'enterprise']),
version: z.string(),
locale: z.string(),
prefix: z.string(),