forked from heavy-duty/stoke
400 lines
13 KiB
Markdown
400 lines
13 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 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}`.
|
|
|
|
## 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`.
|
|
|
|
| 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.
|