stoke/README.md

370 lines
11 KiB
Markdown

# forgejo-cli
A command-line interface for [Forgejo](https://forgejo.org/), built with [Commander.js](https://github.com/tj/commander.js/). It targets the instance at `https://forgejo.heavyduty.builders` and is designed so that every real operation performed against Forgejo becomes a new CLI command.
## Requirements
- Node.js >= 18 (uses the global `fetch` API)
- npm
## Installation
```bash
cd forgejo-cli
npm install
npm link # makes the `forgejo` binary available globally
```
Or run it directly without linking:
```bash
node src/cli.js <command>
```
## Configuration
Authentication state is stored in a JSON file:
- Default: `~/.config/forgejo-cli/config.json`
- Override with `--config <path>` or `FORGEJO_CONFIG_FILE`
The configuration directory is created with permissions `0700` and the file with `0600` so only the owner can read the token.
Example stored config:
```json
{
"url": "https://forgejo.heavyduty.builders",
"login": "kimi-reviewer-andresmgsl",
"username": "kimi-reviewer-andresmgsl",
"email": "andres+4@heavyduty.builders",
"token": "<sha1>",
"tokenId": 42
}
```
## Environment variables
| Variable | Purpose |
| --- | --- |
| `FORGEJO_URL` | Default Forgejo base URL |
| `FORGEJO_USERNAME` | Default username/email for `auth login` |
| `FORGEJO_PASSWORD` | Default password for `auth login` |
| `FORGEJO_CONFIG_FILE` | Path to the config file |
| `FORGEJO_CONFIG_DIR` | Directory for the config file |
| `XDG_CONFIG_HOME` | Followed when resolving the default config directory |
| `GITHUB_TOKEN` | GitHub token used by `repo import` when `--github-token` is omitted |
## Commands
### Global options
```text
-c, --config <path> path to configuration file
-h, --help display help
-V, --version display version
```
### `forgejo auth login`
Authenticate and persist an access token.
```text
Options:
-u, --url <url> Forgejo base URL (default: https://forgejo.heavyduty.builders)
-n, --username <username> account username or email
-p, --password <password> account password
--password-file <path> read account password from a file
-t, --token <token> use an existing personal access token instead of generating one
--token-file <path> read an existing personal access token from a file
--token-name <name> name for the generated token
```
Interactive example:
```bash
forgejo auth login
# prompts for username and password
```
Non-interactive example using environment variables:
```bash
export FORGEJO_USERNAME='kimi-reviewer-andresmgsl'
export FORGEJO_PASSWORD='...'
forgejo auth login
```
Password file example (avoids shell history and special-character issues):
```bash
chmod 600 /run/secrets/forgejo-password
forgejo auth login -n kimi-reviewer-andresmgsl --password-file /run/secrets/forgejo-password
```
Existing token example:
```bash
forgejo auth login -t <personal-access-token>
```
Flow:
1. Calls `GET /api/v1/user` to verify credentials and resolve the canonical `login` name.
2. Calls `POST /api/v1/users/{login}/tokens` to generate a personal access token.
3. Requests the standard non-admin scopes: `read/write` for `activitypub`, `issue`, `misc`, `organization`, `package`, `repository`, and `user`.
4. Writes the token, token id, user details and URL to the config file.
### `forgejo auth logout`
Revoke the stored token remotely and delete the local config.
```bash
forgejo auth logout
```
Flow:
1. Calls `DELETE /api/v1/users/{login}/tokens/{id}` using the stored token.
2. Removes `~/.config/forgejo-cli/config.json`.
### `forgejo auth status`
Display the currently authenticated user.
```bash
forgejo auth status
```
Calls `GET /api/v1/user` with the stored token.
### `forgejo repo create`
Create a new repository for the authenticated user.
```text
Options:
--name <name> repository name (required)
-d, --description <description> repository description
--private make the repository private
--public make the repository public
--auto-init initialize with a README (default: true)
--default-branch <branch> default branch name (default: "main")
```
Example:
```bash
forgejo repo create --name forgejo-cli-test --private \
-d "Test repository created via forgejo-cli"
```
Calls `POST /api/v1/user/repos`.
### `forgejo repo list`
List repositories for the authenticated user.
```text
Options:
-l, --limit <number> maximum repositories to display (default: 50; use 0 for all)
```
```bash
forgejo repo list
```
Calls `GET /api/v1/user/repos` and auto-paginates.
### `forgejo repo import`
Import a remote repository into Forgejo, including git history, issues, pull requests, labels, milestones, releases and wiki.
```text
Options:
--from <clone-addr> source clone URL (required)
--name <name> repository name in Forgejo (required)
--service <service> source service type: git, github, gitea, gitlab, ... (default: github)
--owner <owner> Forgejo owner for the imported repo (default: current user)
-d, --description <description> repository description
--private / --public visibility
--issues migrate issues (default)
--no-issues skip issues
--labels migrate labels (default)
--no-labels skip labels
--milestones migrate milestones (default)
--no-milestones skip milestones
--pull-requests migrate pull requests (default)
--no-pull-requests skip pull requests
--releases migrate releases (default)
--no-releases skip releases
--wiki migrate wiki (default)
--no-wiki skip wiki
--lfs migrate LFS objects
--github-token <token> GitHub token (defaults to GITHUB_TOKEN or `gh auth token`)
```
Example used to mirror `heavy-duty/box`:
```bash
forgejo repo import \
--from https://github.com/heavy-duty/box.git \
--name box \
--service github \
--public \
--issues --labels --milestones --pull-requests --releases --wiki
```
Calls `POST /api/v1/repos/migrate`.
### `forgejo repo import-batch`
Import multiple repositories from a JSON manifest.
```text
Options:
-f, --file <path> path to JSON manifest (required)
--dry-run print the manifest without importing
```
Manifest format:
```json
[
{
"name": "rig",
"from": "https://github.com/heavy-duty/rig.git",
"service": "github",
"private": false,
"issues": true,
"labels": true,
"milestones": true,
"pull_requests": true,
"releases": true,
"wiki": true
}
]
```
Example:
```bash
forgejo repo import-batch -f repos.json
forgejo repo import-batch -f repos.json --dry-run
```
Calls `POST /api/v1/repos/migrate` once per entry.
### `forgejo issue list`
List issues in a repository.
```text
Options:
-o, --owner <owner> repository owner (required)
-r, --repo <repo> repository name (required)
-s, --state <state> open, closed, all (default: open)
-t, --type <type> issues or pulls (default: issues)
-l, --limit <number> maximum issues to display (default: 50; use 0 for all)
```
```bash
forgejo issue list -o kimi-reviewer-andresmgsl -r box -s all -l 0
```
Calls `GET /api/v1/repos/{owner}/{repo}/issues` and auto-paginates.
### `forgejo pr list`
List pull requests in a repository.
```text
Options:
-o, --owner <owner> repository owner (required)
-r, --repo <repo> repository name (required)
-s, --state <state> open, closed, all (default: open)
-l, --limit <number> maximum PRs to display (default: 50; use 0 for all)
```
```bash
forgejo pr list -o kimi-reviewer-andresmgsl -r box -s all -l 0
```
Calls `GET /api/v1/repos/{owner}/{repo}/pulls` and auto-paginates.
### `forgejo branch list`
List branches in a repository.
```text
Options:
-o, --owner <owner> repository owner (required)
-r, --repo <repo> repository name (required)
-l, --limit <number> maximum branches to display (default: 50; use 0 for all)
```
```bash
forgejo branch list -o kimi-reviewer-andresmgsl -r box
```
Calls `GET /api/v1/repos/{owner}/{repo}/branches` and auto-paginates.
### `forgejo collaborator add`
Add a collaborator to a repository.
```text
Options:
-o, --owner <owner> repository owner (required)
-r, --repo <repo> repository name (required)
-u, --user <username> username of the collaborator (required)
--permission <permission> read, write, or admin (default: write)
```
Example:
```bash
forgejo collaborator add -o kimi-reviewer-andresmgsl -r infra -u andres --permission admin
forgejo collaborator add -o kimi-reviewer-andresmgsl -r infra -u dan --permission admin
```
Calls `PUT /api/v1/repos/{owner}/{repo}/collaborators/{user}`.
## Architecture
```text
src/
├── cli.js # Commander program, commands and user I/O
├── api.js # Forgejo API client (fetch wrapper)
└── config.js # Secure filesystem-based config storage
```
- `cli.js` defines commands and options, handles prompts and prints results.
- `api.js` encapsulates all HTTP calls to Forgejo. It supports both Basic auth (for token generation) and token auth (for all other calls).
- `config.js` reads/writes JSON config and enforces restrictive file permissions.
## Security notes
- Tokens are stored on disk with `0600` permissions.
- Passwords are never persisted; they are only used to generate a token.
- Prefer `--password-file` or `FORGEJO_PASSWORD` over `-p` to keep passwords out of shell history and avoid `!` history-expansion issues.
- The generated token name includes the hostname and a timestamp to avoid collisions.
## Verification: heavy-duty repository imports
The heavy-duty repositories were imported into Forgejo under `https://forgejo.heavyduty.builders/kimi-reviewer-andresmgsl`.
| Repository | Visibility | Branches | Commits | Open issues | Total issues | PRs | Labels | Milestones | Releases |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `box` | public | 1 | 306 | 11 | 65 | 92 | 21 | 0 | 4 |
| `rig` | public | 1 | 198 | 10 | 49 | 59 | 21 | 0 | 3 |
| `cast` | public | 1 | 194 | 7 | 67 | 75 | 21 | 0 | 3 |
| `infra` | private | 1 | 86 | 2 | 8 | 22 | 9 | 0 | 0 |
| `handbook` | private | 1 | 29 | 28 | 28 | 13 | 9 | 5 | 0 |
| `incubator` | private | 1 | 1,326 | 1 | 1 | 23 | 9 | 0 | 0 |
Counts match GitHub for all repositories. Git history, issues, pull requests, labels, milestones and releases are included. Discussions are not enabled on any of the source repositories. The `cast` wiki is enabled on GitHub but contains no pages, so nothing was migrated.
## Next steps
The CLI is authenticated and the first full repository import is complete. Every subsequent Forgejo task you need will be added as a new command under `forgejo`.