Ctrl K
ComunidadGuías…

docs/cli.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# cz: the remote client

`cz` is a command-line client for a Cloudzilla host. It is a plain HTTP client over `/api/**`: it never opens the
database or reads `config.yaml`, so it runs on any machine that can reach the server. `cz-admin` is the separate
operator tool that runs on the server host (see [configuration](./configuration.md)).

## Install

Each release attaches a standalone `cz_<tag>_<os>_<arch>.tar.gz` (with a `.sha256`) for every platform the server
release covers, next to the `cloudzilla_<tag>_<os>_<arch>.tar.gz` server archive. The `cz` archive holds one binary;
unpack it onto your `PATH`.

From a checkout, `make build` writes `dist/cz`, or `make build-cz` builds only it. `cz` is cgo-free and needs no CSS or
templ step.

The server archive and Docker image don't include `cz`.

## Logging in

```bash
cz auth login --host https://git.example.com                   # approve in the browser
cz auth login --host https://git.example.com --scope repo:read # ask for less
echo "$TOKEN" | cz auth login --host https://git.example.com --with-token
cz auth status
cz auth logout
```

`login` runs the device flow (RFC 8628) by default. It prints a one-time code and the URL `<host>/login/device`, you
sign in there in a browser (2FA works) and type the code, and `cz` stores the personal access token the server then
issues. Like the `--with-token` path, it verifies the token with one call to `GET /api/user` before saving it, and
prints the host, user and where the token went.

What `cz` shows and does:

- The code (`XXXX-XXXX`) and the URL go to stderr, always. The code is never part of a URL: you read it from your
  terminal and type it, so a link someone sends you can't skip that check.
- The code is copied to the clipboard where a tool exists (`pbcopy`, `wl-copy`, `xclip`, `xsel`, `clip.exe`), silently
  otherwise.
- The plain URL is opened in your browser only when stdin and stdout are both terminals and `--no-browser` isn't set.
- Nothing is read from stdin, so it works headless and over SSH: open the URL on any machine, sign in, and type the
  code. `cz` polls until you approve, deny or the code expires (15 minutes), or you press Ctrl-C. Nothing is stored
  unless the login succeeds.

The token is a normal personal access token named `cz (<hostname>) · <date>`, with no expiry. It appears under Settings,
Access tokens, where you can revoke it. It asks for `repo:write` by default (the scope that admits merging, creating and
forking repositories and pushing); the approval page lets you untick scopes but never add any. `repo:admin` can't be
requested this way: create that token under Settings and use `--with-token`.

An older server without the endpoint answers `404`, and `cz` points you to `--with-token`. In that case, create a
personal access token under your account settings with the scopes from [the table below](#scopes-per-command) and pipe
it in.

| Flag                 | Effect                                                                                                                          |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `--host URL`         | The Cloudzilla URL; `CZ_HOST` is used when omitted                                                                              |
| `--scope S`          | Scope to request: `repo:read`, `repo:write`, `issues:write`, `pulls:write`. Repeatable or space-separated; default `repo:write` |
| `--no-browser`       | Print the URL and code without opening a browser                                                                                |
| `--with-token`       | Skip the browser: read a personal access token from stdin                                                                       |
| `--insecure-storage` | Store the token in a `0600` file instead of the OS keychain                                                                     |

`cz auth status` fails when nobody is logged in.

### Environment variables

| Variable   | Effect                                         |
| ---------- | ---------------------------------------------- |
| `CZ_TOKEN` | Token to use instead of the stored one, for CI |
| `CZ_HOST`  | Host to use instead of the stored one          |

With both set, nothing is read from storage. With `CZ_HOST` alone, `cz` refuses to send the stored token to a different
host than the one it was saved for and asks you to set `CZ_TOKEN` too. `CZ_TOKEN` alone needs a stored login to supply
the host.

### Where the token lives

The token goes to the OS keychain. When none is available, or with `--insecure-storage`, it goes to `credentials.json`
in a `cz` directory under `os.UserConfigDir()` (`~/.config/cz/credentials.json` on Linux,
`~/Library/Application Support/cz/credentials.json` on macOS), mode `0600`. `cz auth status` names the one in use.

## Output

On a terminal, commands print human-readable text. `--json`, or a stdout that isn't a terminal, prints JSON instead.
Errors are one line on stderr prefixed `cz:`, with a non-zero exit: an unreachable host, a bad token (`401`, run
`cz auth login`) and a missing scope each get their own message. A `429` reports the `Retry-After` wait.

A bad token is not an error on a public read. `repo list`, `issue list`, `pr list` and the like on a public repository
succeed with public data, because the server treats a token it can't verify as anonymous on those routes. If a listing
shows only public repositories, run `cz auth status`: it verifies the token and fails when it is bad.

## Scopes per command

A token acts as its user but only on the routes its scopes admit ([Token Scopes](./access-control.md#token-scopes)).
When the server answers `403 insufficient_scope`, `cz` prints the scope from the `WWW-Authenticate` header.

| Command                               | Scope needed                                                 |
| ------------------------------------- | ------------------------------------------------------------ |
| `cz auth login`, `status`             | any scope (calls `GET /api/user`)                            |
| `cz auth logout`                      | none; removes the local login only                           |
| `cz repo list`, `view`                | `repo:read`                                                  |
| `cz repo create`, `fork`              | `repo:write`                                                 |
| `cz repo clone`                       | `repo:read` (git fetch)                                      |
| `cz issue list`, `view`               | `repo:read`                                                  |
| `cz issue create`, `comment`, `close` | `issues:write` (or `repo:write`)                             |
| `cz pr list`, `view`                  | `repo:read`                                                  |
| `cz pr create`, `close`, `review`     | `pulls:write` (or `repo:write`)                              |
| `cz pr merge`                         | `repo:write`. `pulls:write` can't merge or enable auto-merge |
| `cz api <METHOD> <path>`              | whatever the request needs                                   |

## Repositories, issues and pull requests

Commands that take a repository read it from `-R owner/repo`, or from the `origin` remote of the current checkout. A
remote on another host is refused with a message saying so. HTTPS, `ssh://` and `git@host:owner/repo` remotes work.

Body text comes from `--body`, `--body-file` (`-` reads stdin), or `$VISUAL`/`$EDITOR` when run on a terminal with
neither.

```bash
cz repo list [--owner alice]
cz repo view alice/site
cz repo create site [--org acme] [--private] [-d text] [--readme] [--gitignore Go] [--license mit]
cz repo fork alice/site
cz repo clone alice/site [directory] [-- git-clone-flags]

cz issue list [-R alice/site] [--state open|closed]
cz issue view 12
cz issue create -t "Broken link" [-b text] [-l bug] [-a bob]
cz issue comment 12 -b text
cz issue close 12

cz pr list [--state open|closed|merged|all]
cz pr view 7
cz pr create -t "Fix links" [-H branch] [-B main] [-b text] [--draft]
cz pr merge 7 [--ff | --merge | --squash]
cz pr close 7
cz pr review 7 --approve | --request-changes -b text | --comment -b text
```

- `repo list --owner` filters on the client, because the API has no owner filter.
- `issue list --state` and `pr list --state` filter on the client. The server returns every state, and at most 500
  issues, with no paging.
- `issue create` looks up each `--label` by name before creating anything, so an unknown label fails with no issue
  created. If a label or assignee step fails after the issue exists, the error gives its number and URL.
- `pr create` defaults `--head` to the current branch and `--base` to the repository's default branch. It checks that
  the head branch is on the remote and refuses if it isn't; the server would accept any head name.
- `pr merge` needs `repo:write`. A `pulls:write` token is told so. Conflicts, requested changes, branch protection and
  required checks come back as the server's reason with a non-zero exit.
- `pr close` refuses a pull request that is no longer open.
- `pr review --request-changes` and `--comment` need a body.

### How `repo clone` and `pr create` use the token

Both run the system `git`. The token is handed to a URL-scoped credential helper through the `CZ_GIT_TOKEN` environment
variable of the git child process, and `GIT_TERMINAL_PROMPT=0` is set. It never appears in the URL, the arguments,
`.git/config` or shell history. Other processes of the same user can still read a running process's environment (for
example with `ps eww` on some systems), so don't run `cz` on a machine where you don't trust those users.

## cz api

```bash
cz api GET /api/repos/alice/site/issues -f state=open
cz api POST /api/repos/alice/site/issues -f title="Broken link" -f priority=2
cz api POST /api/repos/alice/site/issues --input issue.json
```

- `-f key=value` (repeatable) goes in the query string for `GET` and `HEAD`, and otherwise in a JSON body. `true`,
  `false` and integers are sent as that JSON type.
- `--form` sends the fields as `application/x-www-form-urlencoded` instead of JSON.
- `--input FILE` sends a file as the JSON body (`-` reads stdin). It can't be combined with `-f`.
- The path must start with `/` and is sent to the logged-in host.

## Safety behaviour

- Redirects are never followed. A bad token on a page route redirects to the HTML login, and the `Authorization` header
  must not travel further; `cz` reports the redirect instead.
- The token is sent only to the host it was stored for (see the host-mismatch refusal above).

## What cz can't do

- Admin routes (`/api/admin/*`), account routes (`/api/user/*` such as SSH keys, tokens and 2FA), notifications, gists
  and OAuth apps are closed to tokens whatever their scopes.
- `repo:admin` tokens (collaborators, deploy keys, webhooks, transfer, delete) need every request signed with an SSH
  key. `cz` doesn't sign requests.
- Repository imports aren't available over token auth.
- Instance operation (migrations, backup, password resets) belongs to `cz-admin`, which needs the server's config and
  database.