stoke/README.md

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.