Commit graph

39 commits

Author SHA1 Message Date
sid
373c769d2c refactor(cmd): unify actions/aliases command trees via factory functions
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
The remaining audit finding: cmd/aliases.go rebuilt parallel `run *` and
`workflow *` command subtrees by hand to expose them at top level
(matching gh CLI's `gh run list` ergonomics). That duplication is what
let the `--json` Bool/string mismatch fixed in 0c181df slip in — the
flag was registered correctly under `actions run list` but Bool-typed
under the top-level `run list`, and `wantJSON` silently swallowed the
type-error return.

Switch each `run *` and `workflow *` command from a package-level
`var xxxCmd = &cobra.Command{...}` declaration to a factory function
`newXxxCmd() *cobra.Command` that returns a fully-configured Command
(struct, examples, args, RunE, AND its own flag registrations).

Each parent factory (newRunCmd, newWorkflowCmd) takes a `parentLabel`
string that's appended to the parent's Short/Long, so the alias-tree
variant says "(alias for 'actions run')" without the children diverging.

actions.go init() now does:
  actionsCmd.AddCommand(newRunCmd(""))
  actionsCmd.AddCommand(newWorkflowCmd(""))

aliases.go shrinks from 142 lines to 17 lines:
  rootCmd.AddCommand(newRunCmd(" (alias for 'actions run')"))
  rootCmd.AddCommand(newWorkflowCmd(" (alias for 'actions workflow')"))

Verified: `diff` of `fj run list --help` flags vs `fj actions run list
--help` flags is empty. Both trees produce IDENTICAL surfaces. Future
flag changes touch one factory and propagate to both paths.

Note: secret/variable subcommands aren't aliased so they keep the
package-level var pattern. Only the run/workflow subtrees moved.
2026-05-02 15:56:58 -06:00
sid
155ddb97ba fix(api): validate same-origin before forwarding auth on --paginate
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
Codex flagged: the --paginate loop rebuilt the next request from the raw
`Link: rel="next"` URL and reattached the bearer token without checking
that the next URL was on the same host. Forgejo emits same-origin next-
links in practice, but a buggy or malicious upstream could redirect us
to a foreign host, at which point the token would leak.

Now the loop:
- url.Parse the Link target.
- Resolve relative URLs against the original base (https://<host>/api/v1).
- Refuse to proceed if the resolved URL's scheme isn't https or its host
  doesn't match `host.Hostname`. The error names both the foreign URL
  and the expected origin so the user can tell why pagination stopped.

Verified: same-origin pagination still works (`--paginate` against
forgejo.zerova.net commits returns 44 across 22 pages).
2026-05-02 15:48:59 -06:00
sid
133fb2fea4 feat(cmd): pagination unification + fj api --paginate
Before this, only `release list` walked pages. `repo list`, `pr list` (the
non-filter branch), and `issue list` all passed `PageSize: limit` directly
to the gitea SDK — which silently caps PageSize at 50, so any request for
more than 50 results was truncated to 50 with no warning. `--limit` was
effectively a per-page hint, not a real limit.

## Changes

- New `cmd/paginate.go` — generic `paginateGitea[T any]` that walks pages
  until the response is short or the limit is reached. Uses Go 1.20
  generics so each list command keeps its existing typed slice without
  conversion overhead.

- `repo list` — paginates ListUserRepos.
- `pr list` — paginates ListRepoPullRequests in both branches:
  - With client-side filters (assignee, author, labels, search, draft,
    head, base): pull all pages then filter+limit.
  - Without filters: paginate up to limit.
- `issue list` — paginates ListRepoIssues. Overshoots 2x because the API
  returns both issues AND PRs and we filter PRs out client-side; the
  overshoot keeps us bounded but reduces the chance of returning fewer
  results than `--limit`.

## `fj api --paginate`

Mirrors `gh api --paginate`:
- Follows RFC 5988 `Link: rel="next"` headers (Forgejo emits these on
  list endpoints).
- Concatenates each page's JSON array into a single array via
  `concatPaginatedJSON`. If a page is not a JSON array, errors with a
  clear message — `--paginate` only makes sense for paginatable endpoints.
- GET-only (errors on POST/PUT/DELETE).
- Reuses the same auth and custom headers across pages; the body-size
  limit applies per-page.

Refactored the request execution into a `doOnce` closure so the loop body
isn't a copy of the single-request path.

Verified live:

  $ fj api 'repos/public/claude-code-proxy/commits?limit=2' \
        --paginate --jq '. | length'
  44

(44 = total commits in the repo, walked via Link headers from a 2-per-page
starting query.)

Out of scope for this commit, deferred:
- De-duplicating cmd/aliases.go ↔ cmd/actions.go subtrees (the type
  mismatch they caused is already fixed in the prior commit; the
  duplication itself is polish).
2026-05-02 15:46:22 -06:00
sid
0c181df1d1 fix(cmd): correctness + audit hardening across cmd/ + internal/
Addresses audit findings from a tri-partite review (codex + 2 Claude agents).
Multiple distinct fixes here because they touched overlapping files; happy
to split via interactive rebase if a reviewer prefers.

## Correctness bugs (HIGH)

* `--config` is now actually honored. cmd/root.initConfig fed Viper but
  every command that mattered loaded config via `internal/config.Load()`
  which always read the default path. Added `config.SetExplicitConfigPath`
  consulted by `GetConfigPath`; `--config other.yaml auth login` now writes
  to other.yaml.
  - internal/config/config.go, cmd/root.go

* `--json` now works on `fj run …`, `fj workflow …`, and `fj wiki view`.
  cmd/aliases.go registered `--json` as a Bool but the handlers call
  `wantJSON()` which does `GetString("json")` and silently ignores the
  type-error return. cmd/wiki.go did the inverse (`GetBool("json")` against
  a string-registered flag). Both now use `addJSONFlags`/`wantJSON`/
  `outputJSON` consistently.
  - cmd/aliases.go, cmd/wiki.go

* `fj api` no longer lets endpoints escape the /api/v1 base via
  path-traversal. `fj api '/../admin/users'` previously normalized to
  `/admin/users` because `http.NewRequest` resolves `..` segments —
  silently sending authenticated traffic to non-API routes. Endpoint is
  now parsed, `..` segments are rejected, and JoinPath is used.
  - cmd/api.go

## Design rework (BREAKING — gets rid of the `--json=fields` quirk)

* `--json` flag rebuilt from a string-with-NoOptDefVal=" " sentinel into a
  plain Bool. `--json-fields` keeps comma-separated projection. The two
  are mutually exclusive (`MarkFlagsMutuallyExclusive`). `--jq` composes
  with either or neither. The previous design produced a `--json string[=" "]`
  in --help and required `--json=fields` (with literal "=") because
  `--json fields` was parsed as the bare flag plus a positional. Gone.
  - cmd/json.go: addJSONFlags / wantJSON / outputJSON
  - cmd/api.go: example block reflects the new shape

  Migration: `--json=fields` → `--json-fields fields`. Bare `--json` still
  means "everything as JSON".

* `fj api` now uses `internal/api.SharedHTTPClient` (30 s timeout, pooled)
  instead of constructing a zero-value `&http.Client{}` with no timeout.
  A hung Forgejo no longer pins the CLI indefinitely. Response body is
  also bounded by `io.LimitReader` at 64 MB to prevent OOM-on-self.
  - internal/api/client.go (export SharedHTTPClient), cmd/api.go

* `--hostname` declared as a persistent flag on rootCmd is now the only
  declaration. cmd/auth.go re-declared `--hostname` on three subcommands,
  shadowing the persistent flag — meaning `fj --hostname=X auth login`
  and `fj auth login --hostname=X` went through different code paths
  (viper read vs. local flag read). Local declarations removed.
  - cmd/auth.go

## Hardening (MEDIUM/LOW)

* `--token` on `auth login` now emits a stderr warning when used, since
  it puts the PAT on argv (visible in `ps auxe`/shell history). Flag not
  removed — too disruptive — but discoverable now.
  - cmd/auth.go

* Error handling no longer regex-matches "401"/"403" against rendered
  error strings (would have triggered "auth login" hint for an error
  that just mentioned issue #403). Now relies on typed `*api.APIError`.
  Hints moved to a separate `Hint` field on `CLIError`, so JSON-error
  consumers get clean structure and the human renderer still appends
  "\nHint: …".
  - cmd/errors.go

* `migrateConfigDir` now opens dst with `O_TRUNC` instead of just
  `O_CREATE|O_WRONLY`. Previously a partially-pre-existing dst file
  would have legacy contents overwrite a prefix and leave stale tail
  bytes — silent YAML/token corruption.
  - cmd/root.go (extracted into copyOneConfigFile with proper close handling)

* Config dir created with mode 0700 instead of 0755. `initConfig` warns
  on stderr if the resolved config file is world/group readable
  (`mode & 0o077 != 0`); doesn't fail-close.
  - cmd/root.go

* Network errors (`no such host`, `connection refused`, `i/o timeout`)
  now return a structured `CLIError` with code `ErrNetworkError` and a
  hint, instead of a fmt.Errorf chain.
  - cmd/errors.go

Verified: `go build ./...` and `go test ./...` clean. Live integration
tested against forgejo.zerova.net.

Out of scope, deferred to follow-up commits:
- Pagination unification across `repo list`/`pr list`/`issue list` (only
  `release list` walks pages today; others silently truncate).
- `fj api --paginate` to follow pages like `gh api --paginate`.
- De-duplicating cmd/aliases.go ↔ cmd/actions.go subtrees.
2026-05-02 15:41:48 -06:00
sid
f75b831a53 feat(api): add --json, --json-fields, --jq to fj api
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
`fj api` was the only command that returned raw API JSON without exposing
the same projection/filtering knobs that `fj repo list`, `fj pr list`,
etc. already provide. Callers had to pipe to `python -m json.tool` or
`jq` to extract fields, which is inconsistent and discoverable only
after hitting the gap.

Wire the existing addJSONFlags / wantJSON / outputJSON helpers from
cmd/json.go so the API command participates in the same JSON output
pipeline. No behavioral change when none of the new flags are set —
default still pretty-prints JSON and writes raw bytes for non-JSON
responses.

Verified against live forgejo:

  $ fj api repos/public/claude-code-proxy --jq .full_name
  public/claude-code-proxy

  $ fj api repos/public/claude-code-proxy --json=full_name,description
  { "description": "...", "full_name": "public/claude-code-proxy" }

  $ fj api 'repos/public/claude-code-proxy/commits?limit=3' \
        --jq '.[] | "\(.sha[0:8]) \(.commit.message | split("\n")[0])"'
  8e550b97 Local fork: hardening + ops improvements ...
  b9da198e Harden proxy auth, storage, and conversation access
  6cda3631 Harden streaming, pagination, and config loading

Note: `--json=fields` requires the equals sign because the flag has
NoOptDefVal=" " (so `--json` alone is valid for "everything as JSON").
The Example block in --help documents both the `--json=` form and the
`--json-fields` alias which doesn't have that quirk.
2026-05-02 15:22:44 -06:00
sid
25868adcad bump version to 0.3.2
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
2026-04-26 08:33:17 -06:00
sid
c3e8ad67ed complete fgj → fj rename: env vars, config migration, docs
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
- Rename env vars: FGJ_HOST → FJ_HOST, FGJ_TOKEN → FJ_TOKEN,
  FGJ_FORCE_TTY → FJ_FORCE_TTY, FGJ_PAGER → FJ_PAGER,
  FGJ_BINARY_PATH → FJ_BINARY_PATH (all with legacy fallback)
- Auto-migrate ~/.config/fgj/ → ~/.config/fj/ on first run
- Update man page title, README, CHANGELOG
- Update test fixture labels from [FGJ E2E Test] to [FJ E2E Test]
2026-04-26 08:23:48 -06:00
sid
bc43f6e5a5 rename fgj to fj
Module path, binary name, config dir, help text, and docs
all updated from fgj-sid/fgj to fj.
2026-04-26 08:16:52 -06:00
sid
a6cf9a7096 chore: bump version to 0.3.1
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / functional (push) Has been cancelled
Restores installability via 'go install @latest'. Prior letter-suffix
tags (v0.3.0a..v0.3.0f) aren't valid semver and were ignored by Go's
module resolver, leaving @latest pointing at v0.3.0 which predates
the module-path migration.
2026-04-19 20:54:45 -06:00
sid
c2251d9932 chore: migrate module path to public org
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / functional (push) Has been cancelled
Move from forgejo.zerova.net/sid/fgj-sid to
forgejo.zerova.net/public/fgj-sid to reflect the new public org.
2026-04-11 10:34:34 -06:00
sid
4669d21dea chore: bump version to 0.3.0e
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
2026-03-23 12:57:38 -06:00
sid
c293e233d2 feat: add directory-scoped host defaults (match_dirs) and repo list --limit
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions
Add match_dirs field to host config entries for directory-based host
resolution. When no --hostname flag, FGJ_HOST env var, or git remote is
detected, the longest matching directory prefix determines the host.
Symlinks are resolved on both sides for macOS compatibility (/tmp →
/private/tmp). Also adds --limit/-L flag to repo list.
2026-03-23 12:42:24 -06:00
sid
113505de95 feat: v0.3.0d — add PR checks, iostreams, aliases, and broad enhancements
Add PR checks command, iostreams/text packages for colored table output,
top-level run/workflow aliases matching gh CLI structure. Enhance actions,
issues, PRs, releases, repos, labels, milestones, and wiki commands with
improved flags, JSON output, and error handling.
2026-03-23 12:42:24 -06:00
sid
95da06c003 feat: v0.3.0c — add labels, milestones, wiki, issue dependencies
New commands:
- fgj label list/create/edit/delete
- fgj milestone list/view/create/edit/delete
- fgj wiki list/view/create/edit/delete

Enhanced:
- fgj issue edit --add-dependency/--remove-dependency
2026-03-21 21:50:24 -06:00
sid
7ee5a61910 feat: v0.3.0b — add repo edit command, fix repo create --public flag
- Add `fgj repo edit` for changing visibility, description, homepage,
  and default branch on existing repositories
- Fix `repo create --public` flag which was defined but never read
2026-03-21 21:50:24 -06:00
sid
43e43e7024 feat: v0.3.0a — add api command, pr diff/comment/review, structured errors
New commands:
- fgj api: raw REST API passthrough with field inference and path interpolation
- fgj pr diff: view PR diffs with color, --name-only, --stat
- fgj pr comment: add comments to pull requests
- fgj pr review: approve, request changes, or comment on PRs

Agentic enhancements:
- --json-errors flag for structured JSON error output on stderr
- APIError type wrapping HTTP status codes for machine consumption
- Error codes: auth_required, not_found, api_error, invalid_input, etc.

Docs updated for forgejo.zerova.net/sid/fgj-sid fork.
2026-03-21 21:50:24 -06:00
sid
50191cc542 feat: add PR diff, PR review, and structured error handling commands 2026-03-21 21:50:24 -06:00
Romain Bertrand
a43a79d78f feat: implement repo create command 2026-03-13 17:44:44 +01:00
Romain Bertrand
0b8f67e438 feat: allow tags passing when creating/editing an issue
resolves #27
2026-02-20 17:41:45 +01:00
Romain Bertrand
1420b89fed fix: correctly build the jobs logs url
unfortunately, this is available in gitea, not yet backported to forgejo
2026-01-28 15:16:25 +01:00
Romain Bertrand
a7e5dc5798 lint: find a bunch of issues 2026-01-28 15:16:25 +01:00
Romain Bertrand
4c6de3ad2e feat: add actions workflow enable and disable 2026-01-18 13:11:41 +01:00
Romain Bertrand
c2ee338f1c feat: add actions run watch rerun and cancel 2026-01-18 13:11:41 +01:00
Romain Bertrand
3ccef4e1c6 feat: add json output for list and view commands 2026-01-18 13:11:41 +01:00
Romain Bertrand
fe23f2fce3 feat: add auth logout and token helpers 2026-01-18 13:11:40 +01:00
Romain Bertrand
7bb540bd11 feat: add completion and manpage commands 2026-01-18 13:11:40 +01:00
Romain Bertrand
79df4eb780 feat: implement workflow list/view/run 2026-01-16 10:52:15 +01:00
Romain Bertrand
900bd4ea97 feat: allow passing a comment when closing an issue 2026-01-16 10:03:49 +01:00
Romain Bertrand
fc699e9718 release: prepare for v0.2.0 2026-01-09 13:49:41 +01:00
Romain Bertrand
c0baf4fa3b feat: auto detect hostname 2026-01-06 12:24:07 +01:00
Romain Bertrand
36a473a711 feat: releases 2026-01-05 12:57:37 +01:00
Romain Bertrand
9251487e56 feat: issue edit 2026-01-03 11:49:34 +01:00
Romain Bertrand
3bc37d32a6 feat: "pr create --assignee" implementation 2025-12-24 10:15:50 +01:00
Romain Bertrand
c689c7d171 enhance: clone feature 2025-12-16 12:46:31 +01:00
Romain Bertrand
8db012cb7a feat: actions support 2025-12-09 13:41:08 +01:00
Romain Bertrand
ca17526594 feat: basic initial support for actions 2025-12-08 11:16:35 +01:00
Romain Bertrand
aa2be8587a feat: optional -R when in a git repo 2025-12-08 10:29:32 +01:00
Romain Bertrand
1d94431825 lint: fix linting issues 2025-12-08 10:00:50 +01:00
Romain Bertrand
5b67d39aba feat: initial version of the project 2025-12-08 09:49:07 +01:00