New commands, one per Forgejo API call used to move the heavy-duty
repositories into the new heavy-duty organization:
- stoke org create (POST /api/v1/orgs)
- stoke org repos (GET /api/v1/orgs/{org}/repos)
- stoke org avatar (POST /api/v1/orgs/{org}/avatar)
- stoke repo transfer (POST /api/v1/repos/{owner}/{repo}/transfer)
All commands documented in the README.
476 lines
15 KiB
Markdown
476 lines
15 KiB
Markdown
# stoke
|
|
|
|
> CLI for the heavy-duty forge (Forgejo). Named after the act of feeding and tending a fire — the operator's hand on the forge.
|
|
|
|
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 stoke
|
|
npm install
|
|
npm link # makes the `stoke` 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/stoke/config.json`
|
|
- Override with `--config <path>` or `STOKE_CONFIG_FILE` (or `FORGEJO_CONFIG_FILE` as a fallback)
|
|
|
|
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 |
|
|
| --- | --- |
|
|
| `STOKE_URL` | Default Forgejo base URL |
|
|
| `STOKE_USERNAME` | Default username/email for `auth login` |
|
|
| `STOKE_PASSWORD` | Default password for `auth login` |
|
|
| `STOKE_CONFIG_FILE` | Path to the config file |
|
|
| `STOKE_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 |
|
|
|
|
`FORGEJO_*` variants are still accepted as fallbacks for backward compatibility.
|
|
|
|
## Commands
|
|
|
|
### Global options
|
|
|
|
```text
|
|
-c, --config <path> path to configuration file
|
|
-h, --help display help
|
|
-V, --version display version
|
|
```
|
|
|
|
### `stoke 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
|
|
stoke auth login
|
|
# prompts for username and password
|
|
```
|
|
|
|
Non-interactive example using environment variables:
|
|
|
|
```bash
|
|
export STOKE_USERNAME='kimi-reviewer-andresmgsl'
|
|
export STOKE_PASSWORD='...'
|
|
stoke auth login
|
|
```
|
|
|
|
Password file example (avoids shell history and special-character issues):
|
|
|
|
```bash
|
|
chmod 600 /run/secrets/stoke-password
|
|
stoke auth login -n kimi-reviewer-andresmgsl --password-file /run/secrets/stoke-password
|
|
```
|
|
|
|
Existing token example:
|
|
|
|
```bash
|
|
stoke 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.
|
|
|
|
### `stoke auth logout`
|
|
|
|
Revoke the stored token remotely and delete the local config.
|
|
|
|
```bash
|
|
stoke auth logout
|
|
```
|
|
|
|
Flow:
|
|
|
|
1. Calls `DELETE /api/v1/users/{login}/tokens/{id}` using the stored token.
|
|
2. Removes `~/.config/stoke/config.json`.
|
|
|
|
### `stoke auth status`
|
|
|
|
Display the currently authenticated user.
|
|
|
|
```bash
|
|
stoke auth status
|
|
```
|
|
|
|
Calls `GET /api/v1/user` with the stored token.
|
|
|
|
### `stoke 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
|
|
stoke repo create --name stoke-test --private \
|
|
-d "Test repository created via stoke"
|
|
```
|
|
|
|
Calls `POST /api/v1/user/repos`.
|
|
|
|
### `stoke repo list`
|
|
|
|
List repositories for the authenticated user.
|
|
|
|
```text
|
|
Options:
|
|
-l, --limit <number> maximum repositories to display (default: 50; use 0 for all)
|
|
```
|
|
|
|
```bash
|
|
stoke repo list
|
|
```
|
|
|
|
Calls `GET /api/v1/user/repos` and auto-paginates.
|
|
|
|
### `stoke 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
|
|
stoke 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`.
|
|
|
|
### `stoke 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
|
|
stoke repo import-batch -f repos.json
|
|
stoke repo import-batch -f repos.json --dry-run
|
|
```
|
|
|
|
Calls `POST /api/v1/repos/migrate` once per entry.
|
|
|
|
### `stoke repo transfer`
|
|
|
|
Transfer a repository to a new owner (a user or an organization). The authenticated user must have admin rights on the repository and permission to create repositories under the new owner (e.g. be an organization owner), in which case the transfer completes immediately.
|
|
|
|
```text
|
|
Options:
|
|
-o, --owner <owner> current repository owner (required)
|
|
-r, --repo <repo> repository name (required)
|
|
--to <new-owner> new owner: username or organization name (required)
|
|
```
|
|
|
|
Example used to move the heavy-duty repositories into the `heavy-duty` organization:
|
|
|
|
```bash
|
|
stoke repo transfer -o kimi-reviewer-andresmgsl -r box --to heavy-duty
|
|
```
|
|
|
|
Calls `POST /api/v1/repos/{owner}/{repo}/transfer`.
|
|
|
|
### `stoke 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
|
|
stoke issue list -o kimi-reviewer-andresmgsl -r box -s all -l 0
|
|
```
|
|
|
|
Calls `GET /api/v1/repos/{owner}/{repo}/issues` and auto-paginates.
|
|
|
|
### `stoke 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
|
|
stoke pr list -o kimi-reviewer-andresmgsl -r box -s all -l 0
|
|
```
|
|
|
|
Calls `GET /api/v1/repos/{owner}/{repo}/pulls` and auto-paginates.
|
|
|
|
### `stoke 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
|
|
stoke branch list -o kimi-reviewer-andresmgsl -r box
|
|
```
|
|
|
|
Calls `GET /api/v1/repos/{owner}/{repo}/branches` and auto-paginates.
|
|
|
|
### `stoke 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
|
|
stoke collaborator add -o kimi-reviewer-andresmgsl -r infra -u andres --permission admin
|
|
stoke collaborator add -o kimi-reviewer-andresmgsl -r infra -u dan --permission admin
|
|
```
|
|
|
|
Calls `PUT /api/v1/repos/{owner}/{repo}/collaborators/{user}`.
|
|
|
|
### `stoke org create`
|
|
|
|
Create a new organization. The authenticated user becomes its first owner.
|
|
|
|
```text
|
|
Options:
|
|
--name <name> organization username, used in URLs (required)
|
|
--full-name <full-name> display name of the organization
|
|
-d, --description <description> organization description
|
|
--website <website> organization website
|
|
--location <location> organization location
|
|
--visibility <visibility> public, limited, private (default: public)
|
|
```
|
|
|
|
Example used to create the Heavy Duty Builders organization:
|
|
|
|
```bash
|
|
stoke org create --name heavy-duty --full-name "Heavy Duty Builders" \
|
|
--website https://heavyduty.builders --visibility public
|
|
```
|
|
|
|
Calls `POST /api/v1/orgs`.
|
|
|
|
### `stoke org repos`
|
|
|
|
List repositories owned by an organization.
|
|
|
|
```text
|
|
Options:
|
|
-o, --org <org> organization name (required)
|
|
-l, --limit <number> maximum repositories to display (default: 50; use 0 for all)
|
|
```
|
|
|
|
```bash
|
|
stoke org repos -o heavy-duty -l 0
|
|
```
|
|
|
|
Calls `GET /api/v1/orgs/{org}/repos` and auto-paginates.
|
|
|
|
### `stoke org avatar`
|
|
|
|
Set the avatar (logo) of an organization from a local image file. The image is read, base64-encoded and uploaded; the caller must be an owner of the organization.
|
|
|
|
```text
|
|
Options:
|
|
-o, --org <org> organization name (required)
|
|
-f, --file <path> path to the image file (png, jpeg, gif, ...) (required)
|
|
```
|
|
|
|
Example used to set the Heavy Duty Builders logo (sourced from the official GitHub organization avatar at `https://github.com/heavy-duty.png`):
|
|
|
|
```bash
|
|
stoke org avatar -o heavy-duty -f heavy-duty-logo.png
|
|
```
|
|
|
|
Calls `POST /api/v1/orgs/{org}/avatar`.
|
|
|
|
## 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 `STOKE_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` and later transferred to the `heavy-duty` organization (`https://forgejo.heavyduty.builders/heavy-duty`) using `stoke repo transfer`.
|
|
|
|
| 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 `stoke`.
|
|
|
|
## Why the name `stoke`?
|
|
|
|
The heavy-duty handbook defines a strict naming style for tools:
|
|
|
|
- **one syllable**, plain concrete English — industrial: the shop floor and the site
|
|
- **a repurposed common word**, never coined jargon
|
|
- **a verb⇄noun with a double meaning** that hints at the job
|
|
|
|
Existing tools follow this exactly: `cast` pours a part and is the part; `rig` sets up and is the setup; `jig` holds work and is the fixture; `boss` runs the site and is the supervisor; `hand` works and is the worker; `gig` is a one-off job and the job itself; `gauge` measures and is the instrument.
|
|
|
|
The handbook calls the Forgejo instance **"the forge"** — the coordination home where issues, PRs, reviews, and git state live. A forge only stays useful if someone feeds it fuel and air. *To stoke* a fire is to tend it and keep it burning. This CLI does the same thing programmatically: it creates repos, opens issues, imports projects, adds collaborators — it keeps the forge active.
|
|
|
|
`stoke` also fits the operator relationship. While `jig`, `boss`, `hand`, and `gauge` are autonomous or daemon-like, this tool is the operator's hand on the forge. You don't forge by accident; you stoke on purpose.
|
|
|
|
Other names were considered and rejected:
|
|
|
|
- `forge` — too literal; the forge is the platform, not the tool.
|
|
- `tap` — machining term, but "tapping" is about threading holes, not tending a forge.
|
|
- `weld` / `braze` — join-metal verbs, but the CLI is not primarily joining things.
|
|
- `die` / `mold` / `stamp` — shaping tools, but `die` carries a strong negative meaning and `mold` has the fungus connotation.
|
|
- `quench` — forge-related, but quenching cools/finishes; the CLI starts and feeds work.
|
|
- `draft` / `blast` — air-feeding the forge, but feel like background services, not an operator CLI.
|
|
- `flux` — nice metaphor for flow, but reads as chemistry rather than the forge shop.
|
|
|
|
`stoke` was the only candidate that was clearly forge-specific, operator-facing, one syllable, and already a common verb/noun.
|