docs: switch demo recording to vhs (declarative + reproducible)
Some checks are pending
ci / check (push) Waiting to run
Some checks are pending
ci / check (push) Waiting to run
* scripts/demo.tape: vhs tape file driving a ~30s session: --version, repo view (auto-detect), issue list, pr list, api + jq, selective JSON, help. Catppuccin Mocha, 1100x720. * scripts/record-demo.sh: thin wrapper with preflight checks (vhs + fj installed and authenticated, run from a clone). Outputs assets/demo.gif and assets/demo.mp4. * Removed scripts/_demo-session.sh (the asciinema sketch). vhs is strictly better for scripted demos: deterministic timing, direct GIF/MP4 output, no upload step. * README: replaced the asciinema TODO with the GIF embed (will resolve once assets/demo.gif is recorded and committed). To record: brew install vhs cargo build --release && ln -sf \$PWD/target/release/fj ~/.local/bin/fj fj auth login --host rasterhub.com ./scripts/record-demo.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d58bd312f9
commit
65420678d9
14
README.md
14
README.md
|
|
@ -9,12 +9,18 @@
|
|||
Multi-host from day one. Tokens are stored in your OS keychain.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/demo.gif" alt="fj demo" width="800">
|
||||
</p>
|
||||
|
||||
<!--
|
||||
TODO: replace the line below with an asciinema embed once the v0.1.0
|
||||
release is tagged. Run `./scripts/record-demo.sh` then
|
||||
`asciinema upload dist/demo.cast` and paste the returned URL.
|
||||
The GIF above is generated from scripts/demo.tape via charmbracelet/vhs.
|
||||
To refresh it after a feature change:
|
||||
brew install vhs
|
||||
./scripts/record-demo.sh
|
||||
git commit -am 'docs: refresh demo recording'
|
||||
-->
|
||||
<!-- [](https://asciinema.org/a/REPLACE) -->
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
|
|
|
|||
2
dist/v0.1.0/SHA256SUMS
vendored
Normal file
2
dist/v0.1.0/SHA256SUMS
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
eb5fd0206a5194179f6d1aecee7fe4a4a5434e1f44ed75a525f7f41d18670061 fj-v0.1.0-darwin-aarch64.tar.gz
|
||||
d7102095f051f4af7af1d9dbf34678df85142fdfece71dcab0cc111aeb942c2c fj-v0.1.0-darwin-x86_64.tar.gz
|
||||
BIN
dist/v0.1.0/fj-v0.1.0-darwin-aarch64.tar.gz
vendored
Normal file
BIN
dist/v0.1.0/fj-v0.1.0-darwin-aarch64.tar.gz
vendored
Normal file
Binary file not shown.
121
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/CHANGELOG.md
vendored
Normal file
121
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes will be recorded here. The format follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added (distribution + open-source ready)
|
||||
|
||||
- `LICENSE` (MIT) at the repo root. Cargo.toml had always declared MIT;
|
||||
the file was just missing.
|
||||
- `.forgejo/workflows/release.yml`: on `v*` tags, builds for
|
||||
`darwin-aarch64`, `darwin-x86_64`, `linux-x86_64`; uploads tarballs,
|
||||
`SHA256SUMS`, and a pre-rendered `fj.rb` to the Forgejo release.
|
||||
- Homebrew formula template at `dist/homebrew/fj.rb.tmpl` and
|
||||
`scripts/render-homebrew-formula.sh` to fill SHA256s post-release.
|
||||
Publishes into the existing `rasterandstate/homebrew-tap`.
|
||||
- `.forgejo/issue_template/{bug,feature,api-gap}.md` and
|
||||
`.forgejo/pull_request_template.md` to keep triage cheap.
|
||||
- `scripts/record-demo.sh` + `scripts/_demo-session.sh` to record an
|
||||
asciinema demo; README has a placeholder embed for the v0.1.0 cast.
|
||||
- `docs/compatibility.md`: tested Forgejo versions, caveats for older
|
||||
Gitea, and Forgejo-only endpoints.
|
||||
- One-shot version probe during `fj auth login` that warns when the
|
||||
server reports a pre-7.x version (with tests for the parser).
|
||||
|
||||
### Added (agent-focused Forgejo gaps)
|
||||
|
||||
- `fj issue edit-comment` / `delete-comment`. Lets an agent (or you)
|
||||
fix or remove a wrong comment after the fact.
|
||||
- `fj search code "..."` (and `-R owner/name` to scope to one repo).
|
||||
Powered by Forgejo's `/repos/search/code` endpoint.
|
||||
- `fj pr request-review N user1 user2,user3` and
|
||||
`fj pr unrequest-review N user1`. Distinct from `pr review`, which
|
||||
submits your own review.
|
||||
- `fj repo watch` / `unwatch` / `star` / `unstar` / `starred`.
|
||||
- `fj milestone {list,view,create,edit,close,reopen,delete,assign}`.
|
||||
Includes `fj milestone assign N --milestone ID|none` to attach an
|
||||
issue or PR to a milestone.
|
||||
|
||||
### Added (UX + stability)
|
||||
|
||||
- `--json-fields field1,field2` global flag. gh-style projection on top
|
||||
of any `--json` output, with dotted-path support
|
||||
(`--json-fields owner.login,id`).
|
||||
- 429 / Retry-After honored in the retry loop with a 30 s cap. Wiremock
|
||||
test added.
|
||||
- `did you mean` suggestions on typo'd subcommands via clap's
|
||||
`suggestions` feature.
|
||||
- `fj auth token` and `fj auth status --show-token` now refuse to write
|
||||
to a TTY (use `--force` to override). Avoids accidental shoulder-
|
||||
surfing or capture in shell history.
|
||||
- `tokio::signal::ctrl_c()` race in `cli::run` so the pager guard drops
|
||||
cleanly on SIGINT.
|
||||
- 10 wiremock-backed HTTP client integration tests covering retry
|
||||
behavior (5xx, 429), header forwarding, pagination, and panic-free
|
||||
error paths.
|
||||
- `Client::for_base_url` test constructor pointing at an arbitrary URL.
|
||||
- `.forgejo/workflows/ci.yml` runs the same gate as the pre-push hook
|
||||
on every push and PR.
|
||||
|
||||
### Added (docs)
|
||||
|
||||
- `SECURITY.md` covering threat model, known sharp edges, and reporting.
|
||||
- `docs/gh-to-fj.md` — complete command-by-command mapping.
|
||||
- `docs/faq.md` — common questions about tokens, hosts, debug,
|
||||
scripting, plugins.
|
||||
|
||||
### Changed
|
||||
|
||||
- Trimmed dependencies (no more `indicatif`, `futures-util`,
|
||||
`is-terminal`, `textwrap`, `tempfile` in prod). Dropped reqwest
|
||||
features we don't use (`stream`, `brotli`). Release profile uses
|
||||
`lto = "fat"` and `panic = "abort"`.
|
||||
- HTTP retry loop builds the request once and clones via
|
||||
`reqwest::Request::try_clone` per attempt.
|
||||
- Binary size: 5.94 MB → 4.15 MB stripped (-30%).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Shell injection** in `fj auth setup-git`. The hostname now must
|
||||
match a strict DNS pattern before being interpolated into the
|
||||
credential-helper string, and we call `git config` directly with
|
||||
separate args instead of going through `sh -c`.
|
||||
- **Pager won't compile on Windows**. The libc-based `dup2` redirect
|
||||
now lives behind `#[cfg(unix)]`; non-Unix gets a no-op stub that
|
||||
returns `None` from `maybe_start`.
|
||||
- Removed the unsafe `std::env::set_var("FJ_NO_PAGER")` from dispatch.
|
||||
`--no-pager` is now threaded into `pager::maybe_start(force_disabled)`.
|
||||
- Replaced the panicking `.expect("token contains invalid header chars")`
|
||||
in `auth_headers` with a typed error.
|
||||
|
||||
## 0.1.0 — 2026-05-13
|
||||
|
||||
Initial release. Multi-host Forgejo CLI with feature parity to `gh`
|
||||
across the surface Forgejo exposes. Commands:
|
||||
|
||||
- `auth`: login, status, logout, list, switch, token, refresh, setup-git
|
||||
- `repo`: list, view, clone, create, fork, sync, edit, rename, archive,
|
||||
unarchive, delete, branches, topics, mirror, mirror-sync
|
||||
- `issue`: list, view, create, edit, close, reopen, comment, develop
|
||||
- `pr`: list, view, create, edit, diff, commits, files, checks, ready,
|
||||
review, status, checkout, merge, close, reopen
|
||||
- `release`: list, view, create, edit, delete, upload, download
|
||||
- `label`, `run`, `secret`, `variable`, `search`, `browse`, `status`,
|
||||
`org`, `ssh-key`, `gpg-key`, `alias`, `config`, `protect`, `hook`,
|
||||
`extension`, `gist`, `api`, `completion`, `man`
|
||||
|
||||
Other highlights:
|
||||
|
||||
- Repo auto-detection from `upstream` / `origin` git remote.
|
||||
- `--web` flag on all list/view subcommands.
|
||||
- `$EDITOR` integration for body inputs.
|
||||
- `fj api` with `-H`, `-X`, `-f`, `-F`, `--paginate`, `--include`,
|
||||
`--silent`, `--jq` (dot-paths, `[N]`/`[-N]`, pipes).
|
||||
- `--debug` / `FJ_DEBUG` request logging.
|
||||
- Tokens in the OS keychain.
|
||||
- Pager via `dup2` redirect to `$FJ_PAGER` / `$PAGER` / `less -FRX`.
|
||||
- Pre-push hook running fmt, clippy `-D warnings`, tests, and release
|
||||
build before any push. Live API smoke gated on `FJ_E2E=1`.
|
||||
21
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/LICENSE
vendored
Normal file
21
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Stephen Way
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
149
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/README.md
vendored
Normal file
149
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/README.md
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# fj
|
||||
|
||||
A command-line tool for [Forgejo](https://forgejo.org) instances, in the spirit of GitHub's `gh`.
|
||||
|
||||
Multi-host from day one. Tokens are stored in your OS keychain.
|
||||
|
||||
<!--
|
||||
TODO: replace the line below with an asciinema embed once the v0.1.0
|
||||
release is tagged. Run `./scripts/record-demo.sh` then
|
||||
`asciinema upload dist/demo.cast` and paste the returned URL.
|
||||
-->
|
||||
<!-- [](https://asciinema.org/a/REPLACE) -->
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
# Homebrew (macOS + Linux):
|
||||
brew tap rasterandstate/tap
|
||||
brew install fj
|
||||
|
||||
# From source:
|
||||
cargo install --path .
|
||||
# or
|
||||
cargo build --release && cp target/release/fj ~/.local/bin/fj
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
fj auth login # add a host and token
|
||||
fj auth status # show signed-in hosts
|
||||
fj repo list # repos you own
|
||||
fj repo view owner/name # repo overview
|
||||
fj issue list -R owner/name # issues
|
||||
fj pr list -R owner/name --state open # pull requests
|
||||
fj api /repos/search -X GET -f q=foo # raw API escape hatch
|
||||
```
|
||||
|
||||
`-R/--repo` is optional inside a git clone. fj detects the upstream from
|
||||
`upstream`, then `origin`, then any other remote.
|
||||
|
||||
## Commands
|
||||
|
||||
| Group | Common operations |
|
||||
| ---------- | --------------------------------------------------------------------------------- |
|
||||
| `auth` | login, status, logout, list, switch, token, refresh, setup-git |
|
||||
| `repo` | list, view, clone, create, fork, sync, edit, rename, archive, unarchive, delete, branches, topics, mirror, mirror-sync, watch, unwatch, star, unstar, starred |
|
||||
| `issue` | list, view, create, edit, close, reopen, comment, edit-comment, delete-comment, develop |
|
||||
| `pr` | list, view, create, edit, diff, commits, files, checks, ready, review, request-review, unrequest-review, status, checkout, merge, close, reopen |
|
||||
| `release` | list, view, create, edit, delete, upload, download |
|
||||
| `label` | list, create, edit, delete |
|
||||
| `milestone`| list, view, create, edit, close, reopen, delete, assign |
|
||||
| `run` | list, view, rerun, cancel (Forgejo Actions workflow runs) |
|
||||
| `secret` | list, set, delete (Actions secrets) |
|
||||
| `variable` | list, set, delete (Actions variables) |
|
||||
| `search` | repos, issues, prs, users, code |
|
||||
| `browse` | open the current repo (or a path within it) in your browser |
|
||||
| `status` | notifications inbox + `--mark-read` |
|
||||
| `org` | list, view, teams |
|
||||
| `ssh-key` | list, add, delete |
|
||||
| `gpg-key` | list, add, delete |
|
||||
| `alias` | list, set, delete |
|
||||
| `config` | get, set, list, path |
|
||||
| `protect` | list, view, set, delete branch protection rules |
|
||||
| `hook` | list, create, delete, test webhooks |
|
||||
| `gist` | list, create (thin wrapper over `gist-*` repos) |
|
||||
| `extension`| list, run discovered `fj-<name>` plugins on PATH |
|
||||
| `api` | raw HTTP with `-X`, `-f`, `-F`, `-H`, `-q` (jq-ish), `--paginate`, `--include` |
|
||||
| `completion`| Print shell completions (bash, zsh, fish, powershell, elvish) |
|
||||
| `man` | Generate man pages into a directory |
|
||||
|
||||
## Global flags
|
||||
|
||||
- `--host <hostname>` (or `FJ_HOST`): pick the host explicitly.
|
||||
- `--debug` (or `FJ_DEBUG=1`): log every HTTP request to stderr.
|
||||
- `--no-pager` (or `FJ_NO_PAGER=1`): skip the pager.
|
||||
- `--json-fields foo,bar`: gh-style projection on top of any `--json`
|
||||
output. Dotted paths supported (`--json-fields owner.login,id`).
|
||||
- `--web` on most list/view subcommands: open the relevant page in
|
||||
your default browser.
|
||||
|
||||
## Config
|
||||
|
||||
- Hosts and the current host live in `$XDG_CONFIG_HOME/fj/hosts.toml`
|
||||
(`~/Library/Application Support/fj/hosts.toml` on macOS).
|
||||
- Aliases in `aliases.toml`. Preferences in `config.toml`.
|
||||
- Tokens live in the OS keychain under service `fj`, keyed by hostname.
|
||||
|
||||
## Aliases
|
||||
|
||||
```sh
|
||||
fj alias set co "pr checkout"
|
||||
fj co 42 # equivalent to: fj pr checkout 42
|
||||
```
|
||||
|
||||
## Extensions
|
||||
|
||||
Any executable on `$PATH` named `fj-<name>` is invokable as `fj <name> [args]`.
|
||||
|
||||
```sh
|
||||
fj extension list # show discovered plugins
|
||||
fj my-plugin some-arg # invokes `fj-my-plugin some-arg`
|
||||
```
|
||||
|
||||
## Plugin-style usage of `fj api`
|
||||
|
||||
```sh
|
||||
# Auto-paginate a list endpoint into a single JSON array:
|
||||
fj api /repos/search -f q=fj --paginate -q .
|
||||
|
||||
# Pass a custom request header:
|
||||
fj api /user -H "X-Trace-Id: foo"
|
||||
|
||||
# Show response headers along with the body:
|
||||
fj api /version --include
|
||||
|
||||
# Send a literal JSON body:
|
||||
fj api /repos/migrate --input '{"clone_addr":"...","repo_name":"x","repo_owner":"y"}'
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
- `hooks/pre-push` runs `cargo fmt --check`, `cargo clippy -D warnings`,
|
||||
`cargo test`, and a release build before any push.
|
||||
- With `FJ_E2E=1`, the hook also runs `scripts/e2e-smoke.sh` against the
|
||||
configured host.
|
||||
- Install via `./scripts/install-hooks.sh`.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cargo build --release # ~4 MB stripped binary at target/release/fj
|
||||
./target/release/fj completion zsh > ~/.zfunc/_fj
|
||||
./target/release/fj man -o ~/man/man1
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`docs/architecture.md`](docs/architecture.md) — module graph, HTTP
|
||||
funnel, pager + SIGINT, repo resolution
|
||||
- [`docs/jq.md`](docs/jq.md) — `fj api --jq` projection syntax
|
||||
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — keychain
|
||||
prompts, 401s, hook bypass, pager opt-out, alias precedence
|
||||
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — build / test / release workflow
|
||||
- [`CHANGELOG.md`](CHANGELOG.md) — release notes
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
BIN
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/fj
vendored
Executable file
BIN
dist/v0.1.0/fj-v0.1.0-darwin-aarch64/fj
vendored
Executable file
Binary file not shown.
BIN
dist/v0.1.0/fj-v0.1.0-darwin-x86_64.tar.gz
vendored
Normal file
BIN
dist/v0.1.0/fj-v0.1.0-darwin-x86_64.tar.gz
vendored
Normal file
Binary file not shown.
121
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/CHANGELOG.md
vendored
Normal file
121
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes will be recorded here. The format follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added (distribution + open-source ready)
|
||||
|
||||
- `LICENSE` (MIT) at the repo root. Cargo.toml had always declared MIT;
|
||||
the file was just missing.
|
||||
- `.forgejo/workflows/release.yml`: on `v*` tags, builds for
|
||||
`darwin-aarch64`, `darwin-x86_64`, `linux-x86_64`; uploads tarballs,
|
||||
`SHA256SUMS`, and a pre-rendered `fj.rb` to the Forgejo release.
|
||||
- Homebrew formula template at `dist/homebrew/fj.rb.tmpl` and
|
||||
`scripts/render-homebrew-formula.sh` to fill SHA256s post-release.
|
||||
Publishes into the existing `rasterandstate/homebrew-tap`.
|
||||
- `.forgejo/issue_template/{bug,feature,api-gap}.md` and
|
||||
`.forgejo/pull_request_template.md` to keep triage cheap.
|
||||
- `scripts/record-demo.sh` + `scripts/_demo-session.sh` to record an
|
||||
asciinema demo; README has a placeholder embed for the v0.1.0 cast.
|
||||
- `docs/compatibility.md`: tested Forgejo versions, caveats for older
|
||||
Gitea, and Forgejo-only endpoints.
|
||||
- One-shot version probe during `fj auth login` that warns when the
|
||||
server reports a pre-7.x version (with tests for the parser).
|
||||
|
||||
### Added (agent-focused Forgejo gaps)
|
||||
|
||||
- `fj issue edit-comment` / `delete-comment`. Lets an agent (or you)
|
||||
fix or remove a wrong comment after the fact.
|
||||
- `fj search code "..."` (and `-R owner/name` to scope to one repo).
|
||||
Powered by Forgejo's `/repos/search/code` endpoint.
|
||||
- `fj pr request-review N user1 user2,user3` and
|
||||
`fj pr unrequest-review N user1`. Distinct from `pr review`, which
|
||||
submits your own review.
|
||||
- `fj repo watch` / `unwatch` / `star` / `unstar` / `starred`.
|
||||
- `fj milestone {list,view,create,edit,close,reopen,delete,assign}`.
|
||||
Includes `fj milestone assign N --milestone ID|none` to attach an
|
||||
issue or PR to a milestone.
|
||||
|
||||
### Added (UX + stability)
|
||||
|
||||
- `--json-fields field1,field2` global flag. gh-style projection on top
|
||||
of any `--json` output, with dotted-path support
|
||||
(`--json-fields owner.login,id`).
|
||||
- 429 / Retry-After honored in the retry loop with a 30 s cap. Wiremock
|
||||
test added.
|
||||
- `did you mean` suggestions on typo'd subcommands via clap's
|
||||
`suggestions` feature.
|
||||
- `fj auth token` and `fj auth status --show-token` now refuse to write
|
||||
to a TTY (use `--force` to override). Avoids accidental shoulder-
|
||||
surfing or capture in shell history.
|
||||
- `tokio::signal::ctrl_c()` race in `cli::run` so the pager guard drops
|
||||
cleanly on SIGINT.
|
||||
- 10 wiremock-backed HTTP client integration tests covering retry
|
||||
behavior (5xx, 429), header forwarding, pagination, and panic-free
|
||||
error paths.
|
||||
- `Client::for_base_url` test constructor pointing at an arbitrary URL.
|
||||
- `.forgejo/workflows/ci.yml` runs the same gate as the pre-push hook
|
||||
on every push and PR.
|
||||
|
||||
### Added (docs)
|
||||
|
||||
- `SECURITY.md` covering threat model, known sharp edges, and reporting.
|
||||
- `docs/gh-to-fj.md` — complete command-by-command mapping.
|
||||
- `docs/faq.md` — common questions about tokens, hosts, debug,
|
||||
scripting, plugins.
|
||||
|
||||
### Changed
|
||||
|
||||
- Trimmed dependencies (no more `indicatif`, `futures-util`,
|
||||
`is-terminal`, `textwrap`, `tempfile` in prod). Dropped reqwest
|
||||
features we don't use (`stream`, `brotli`). Release profile uses
|
||||
`lto = "fat"` and `panic = "abort"`.
|
||||
- HTTP retry loop builds the request once and clones via
|
||||
`reqwest::Request::try_clone` per attempt.
|
||||
- Binary size: 5.94 MB → 4.15 MB stripped (-30%).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Shell injection** in `fj auth setup-git`. The hostname now must
|
||||
match a strict DNS pattern before being interpolated into the
|
||||
credential-helper string, and we call `git config` directly with
|
||||
separate args instead of going through `sh -c`.
|
||||
- **Pager won't compile on Windows**. The libc-based `dup2` redirect
|
||||
now lives behind `#[cfg(unix)]`; non-Unix gets a no-op stub that
|
||||
returns `None` from `maybe_start`.
|
||||
- Removed the unsafe `std::env::set_var("FJ_NO_PAGER")` from dispatch.
|
||||
`--no-pager` is now threaded into `pager::maybe_start(force_disabled)`.
|
||||
- Replaced the panicking `.expect("token contains invalid header chars")`
|
||||
in `auth_headers` with a typed error.
|
||||
|
||||
## 0.1.0 — 2026-05-13
|
||||
|
||||
Initial release. Multi-host Forgejo CLI with feature parity to `gh`
|
||||
across the surface Forgejo exposes. Commands:
|
||||
|
||||
- `auth`: login, status, logout, list, switch, token, refresh, setup-git
|
||||
- `repo`: list, view, clone, create, fork, sync, edit, rename, archive,
|
||||
unarchive, delete, branches, topics, mirror, mirror-sync
|
||||
- `issue`: list, view, create, edit, close, reopen, comment, develop
|
||||
- `pr`: list, view, create, edit, diff, commits, files, checks, ready,
|
||||
review, status, checkout, merge, close, reopen
|
||||
- `release`: list, view, create, edit, delete, upload, download
|
||||
- `label`, `run`, `secret`, `variable`, `search`, `browse`, `status`,
|
||||
`org`, `ssh-key`, `gpg-key`, `alias`, `config`, `protect`, `hook`,
|
||||
`extension`, `gist`, `api`, `completion`, `man`
|
||||
|
||||
Other highlights:
|
||||
|
||||
- Repo auto-detection from `upstream` / `origin` git remote.
|
||||
- `--web` flag on all list/view subcommands.
|
||||
- `$EDITOR` integration for body inputs.
|
||||
- `fj api` with `-H`, `-X`, `-f`, `-F`, `--paginate`, `--include`,
|
||||
`--silent`, `--jq` (dot-paths, `[N]`/`[-N]`, pipes).
|
||||
- `--debug` / `FJ_DEBUG` request logging.
|
||||
- Tokens in the OS keychain.
|
||||
- Pager via `dup2` redirect to `$FJ_PAGER` / `$PAGER` / `less -FRX`.
|
||||
- Pre-push hook running fmt, clippy `-D warnings`, tests, and release
|
||||
build before any push. Live API smoke gated on `FJ_E2E=1`.
|
||||
21
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/LICENSE
vendored
Normal file
21
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Stephen Way
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
149
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/README.md
vendored
Normal file
149
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/README.md
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# fj
|
||||
|
||||
A command-line tool for [Forgejo](https://forgejo.org) instances, in the spirit of GitHub's `gh`.
|
||||
|
||||
Multi-host from day one. Tokens are stored in your OS keychain.
|
||||
|
||||
<!--
|
||||
TODO: replace the line below with an asciinema embed once the v0.1.0
|
||||
release is tagged. Run `./scripts/record-demo.sh` then
|
||||
`asciinema upload dist/demo.cast` and paste the returned URL.
|
||||
-->
|
||||
<!-- [](https://asciinema.org/a/REPLACE) -->
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
# Homebrew (macOS + Linux):
|
||||
brew tap rasterandstate/tap
|
||||
brew install fj
|
||||
|
||||
# From source:
|
||||
cargo install --path .
|
||||
# or
|
||||
cargo build --release && cp target/release/fj ~/.local/bin/fj
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
fj auth login # add a host and token
|
||||
fj auth status # show signed-in hosts
|
||||
fj repo list # repos you own
|
||||
fj repo view owner/name # repo overview
|
||||
fj issue list -R owner/name # issues
|
||||
fj pr list -R owner/name --state open # pull requests
|
||||
fj api /repos/search -X GET -f q=foo # raw API escape hatch
|
||||
```
|
||||
|
||||
`-R/--repo` is optional inside a git clone. fj detects the upstream from
|
||||
`upstream`, then `origin`, then any other remote.
|
||||
|
||||
## Commands
|
||||
|
||||
| Group | Common operations |
|
||||
| ---------- | --------------------------------------------------------------------------------- |
|
||||
| `auth` | login, status, logout, list, switch, token, refresh, setup-git |
|
||||
| `repo` | list, view, clone, create, fork, sync, edit, rename, archive, unarchive, delete, branches, topics, mirror, mirror-sync, watch, unwatch, star, unstar, starred |
|
||||
| `issue` | list, view, create, edit, close, reopen, comment, edit-comment, delete-comment, develop |
|
||||
| `pr` | list, view, create, edit, diff, commits, files, checks, ready, review, request-review, unrequest-review, status, checkout, merge, close, reopen |
|
||||
| `release` | list, view, create, edit, delete, upload, download |
|
||||
| `label` | list, create, edit, delete |
|
||||
| `milestone`| list, view, create, edit, close, reopen, delete, assign |
|
||||
| `run` | list, view, rerun, cancel (Forgejo Actions workflow runs) |
|
||||
| `secret` | list, set, delete (Actions secrets) |
|
||||
| `variable` | list, set, delete (Actions variables) |
|
||||
| `search` | repos, issues, prs, users, code |
|
||||
| `browse` | open the current repo (or a path within it) in your browser |
|
||||
| `status` | notifications inbox + `--mark-read` |
|
||||
| `org` | list, view, teams |
|
||||
| `ssh-key` | list, add, delete |
|
||||
| `gpg-key` | list, add, delete |
|
||||
| `alias` | list, set, delete |
|
||||
| `config` | get, set, list, path |
|
||||
| `protect` | list, view, set, delete branch protection rules |
|
||||
| `hook` | list, create, delete, test webhooks |
|
||||
| `gist` | list, create (thin wrapper over `gist-*` repos) |
|
||||
| `extension`| list, run discovered `fj-<name>` plugins on PATH |
|
||||
| `api` | raw HTTP with `-X`, `-f`, `-F`, `-H`, `-q` (jq-ish), `--paginate`, `--include` |
|
||||
| `completion`| Print shell completions (bash, zsh, fish, powershell, elvish) |
|
||||
| `man` | Generate man pages into a directory |
|
||||
|
||||
## Global flags
|
||||
|
||||
- `--host <hostname>` (or `FJ_HOST`): pick the host explicitly.
|
||||
- `--debug` (or `FJ_DEBUG=1`): log every HTTP request to stderr.
|
||||
- `--no-pager` (or `FJ_NO_PAGER=1`): skip the pager.
|
||||
- `--json-fields foo,bar`: gh-style projection on top of any `--json`
|
||||
output. Dotted paths supported (`--json-fields owner.login,id`).
|
||||
- `--web` on most list/view subcommands: open the relevant page in
|
||||
your default browser.
|
||||
|
||||
## Config
|
||||
|
||||
- Hosts and the current host live in `$XDG_CONFIG_HOME/fj/hosts.toml`
|
||||
(`~/Library/Application Support/fj/hosts.toml` on macOS).
|
||||
- Aliases in `aliases.toml`. Preferences in `config.toml`.
|
||||
- Tokens live in the OS keychain under service `fj`, keyed by hostname.
|
||||
|
||||
## Aliases
|
||||
|
||||
```sh
|
||||
fj alias set co "pr checkout"
|
||||
fj co 42 # equivalent to: fj pr checkout 42
|
||||
```
|
||||
|
||||
## Extensions
|
||||
|
||||
Any executable on `$PATH` named `fj-<name>` is invokable as `fj <name> [args]`.
|
||||
|
||||
```sh
|
||||
fj extension list # show discovered plugins
|
||||
fj my-plugin some-arg # invokes `fj-my-plugin some-arg`
|
||||
```
|
||||
|
||||
## Plugin-style usage of `fj api`
|
||||
|
||||
```sh
|
||||
# Auto-paginate a list endpoint into a single JSON array:
|
||||
fj api /repos/search -f q=fj --paginate -q .
|
||||
|
||||
# Pass a custom request header:
|
||||
fj api /user -H "X-Trace-Id: foo"
|
||||
|
||||
# Show response headers along with the body:
|
||||
fj api /version --include
|
||||
|
||||
# Send a literal JSON body:
|
||||
fj api /repos/migrate --input '{"clone_addr":"...","repo_name":"x","repo_owner":"y"}'
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
- `hooks/pre-push` runs `cargo fmt --check`, `cargo clippy -D warnings`,
|
||||
`cargo test`, and a release build before any push.
|
||||
- With `FJ_E2E=1`, the hook also runs `scripts/e2e-smoke.sh` against the
|
||||
configured host.
|
||||
- Install via `./scripts/install-hooks.sh`.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cargo build --release # ~4 MB stripped binary at target/release/fj
|
||||
./target/release/fj completion zsh > ~/.zfunc/_fj
|
||||
./target/release/fj man -o ~/man/man1
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`docs/architecture.md`](docs/architecture.md) — module graph, HTTP
|
||||
funnel, pager + SIGINT, repo resolution
|
||||
- [`docs/jq.md`](docs/jq.md) — `fj api --jq` projection syntax
|
||||
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — keychain
|
||||
prompts, 401s, hook bypass, pager opt-out, alias precedence
|
||||
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — build / test / release workflow
|
||||
- [`CHANGELOG.md`](CHANGELOG.md) — release notes
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
BIN
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/fj
vendored
Executable file
BIN
dist/v0.1.0/fj-v0.1.0-darwin-x86_64/fj
vendored
Executable file
Binary file not shown.
|
|
@ -1,38 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Drives an asciinema recording. Don't invoke directly; run record-demo.sh.
|
||||
|
||||
set -u
|
||||
PS1='\$ '
|
||||
export PS1
|
||||
|
||||
PAUSE_BETWEEN=0.6
|
||||
PAUSE_AFTER=1.2
|
||||
|
||||
say() { printf '\033[1;34m# %s\033[0m\n' "$*"; sleep "$PAUSE_BETWEEN"; }
|
||||
do_() { printf '\033[1m$ %s\033[0m\n' "$*"; sleep "$PAUSE_BETWEEN"; eval "$@"; sleep "$PAUSE_AFTER"; }
|
||||
|
||||
clear
|
||||
say "fj: a CLI for Forgejo. Multi-host, tokens in the keychain."
|
||||
do_ "fj --version"
|
||||
|
||||
say "Inside a clone, no flags needed: fj infers the repo from your git remote."
|
||||
do_ "fj repo view | head -8"
|
||||
|
||||
say "Issues, PRs, releases all work the same way."
|
||||
do_ "fj issue list --state all -L 5"
|
||||
do_ "fj pr list --state all -L 5"
|
||||
|
||||
say "The api escape hatch with a jq-ish projector."
|
||||
do_ "fj api /version"
|
||||
do_ "fj api /user -q .login"
|
||||
|
||||
say "Selective JSON for scripting."
|
||||
do_ "fj repo list -L 3 --json --json-fields full_name,private"
|
||||
|
||||
say "Or just browse on the web."
|
||||
say " fj browse src/main.rs"
|
||||
sleep 1.5
|
||||
|
||||
clear
|
||||
say "60+ subcommands. Try fj --help."
|
||||
sleep 2
|
||||
93
scripts/demo.tape
Normal file
93
scripts/demo.tape
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# vhs tape file for the fj README demo.
|
||||
#
|
||||
# Reproduce with:
|
||||
# brew install vhs
|
||||
# ./scripts/record-demo.sh
|
||||
#
|
||||
# The tape is the source of truth. Edit it, re-record, commit the
|
||||
# updated assets/demo.gif and (optionally) assets/demo.mp4.
|
||||
#
|
||||
# Pre-requisites the recorder needs:
|
||||
# * `fj` on PATH (build it via `cargo build --release` and symlink)
|
||||
# * `fj auth login --host rasterhub.com` already completed
|
||||
# * Run from inside the fj clone (so `fj repo view` resolves via the
|
||||
# git remote)
|
||||
|
||||
Output assets/demo.gif
|
||||
Output assets/demo.mp4
|
||||
|
||||
Set Theme "Catppuccin Mocha"
|
||||
Set FontSize 16
|
||||
Set Width 1100
|
||||
Set Height 720
|
||||
Set TypingSpeed 50ms
|
||||
Set Padding 24
|
||||
Set PlaybackSpeed 1.0
|
||||
Set Shell zsh
|
||||
|
||||
# Establish a clean prompt.
|
||||
Hide
|
||||
Type "clear"
|
||||
Enter
|
||||
Show
|
||||
Sleep 600ms
|
||||
|
||||
Type "fj --version"
|
||||
Sleep 300ms
|
||||
Enter
|
||||
Sleep 1200ms
|
||||
|
||||
Type "# inside a clone, fj infers the repo from the git remote"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 600ms
|
||||
|
||||
Type "fj repo view | head -10"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 2200ms
|
||||
|
||||
Type "# issues, PRs, releases — same shape"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "fj issue list --state all -L 5"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 2000ms
|
||||
|
||||
Type "fj pr list --state all -L 5"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 2200ms
|
||||
|
||||
Type "# api escape hatch with jq-ish projection"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "fj api /version"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 1500ms
|
||||
|
||||
Type "fj api /user -q .login"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 1500ms
|
||||
|
||||
Type "# gh-style selective JSON for scripts"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "fj repo list -L 3 --json --json-fields full_name,private"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 2800ms
|
||||
|
||||
Type "fj --help | head -20"
|
||||
Sleep 200ms
|
||||
Enter
|
||||
Sleep 3000ms
|
||||
Loading…
Reference in a new issue