Shim gh pr list and gh repo view for Forgejo repositories
Added Forgejo-backed implementations for the two commands that were still falling through to stock GitHub CLI, so Codex.app has more of the GitHub-shaped metadata it expects in Forgejo-only repositories.
Summary
The shim now routes gh pr list and gh repo view for allowlisted Forgejo hosts. Both commands return compact GitHub-shaped JSON for the fields most likely to be requested by Codex.app and similar tools.
Changes Made
- Expanded command routing so
gh pr listandgh repo viewno longer delegate to stockghin allowlisted Forgejo repositories. - Added a Forgejo repository metadata request through
ForgejoClient.get_repo. - Added repository normalization for fields such as
nameWithOwner,url,sshUrl,defaultBranchRef, andowner. - Added PR list parsing for common flags including
--json,--jq,--state,--limit,--head,--base, and--repo. - Updated tests and README support documentation.
- Tightened the global Codex instructions so future turn documents must use
@pierre/diffs/ssrfor offline diff rendering.
Context
Codex.app showed “GitHub CLI unavailable” even though the installed wrapper was healthy and gh pr create, gh pr view, and gh pr status worked. The missing piece was that gh pr list and gh repo view were outside the supported routing table, so they fell through to the real GitHub CLI, which cannot understand a Forgejo-only remote.
During documentation, the first draft used client-side Diffs rendering with a plain fallback. That was corrected to static @pierre/diffs/ssr output, and the global Codex instructions were clarified to make SSR mandatory for future diff snippets.
Important Implementation Details
The route decision remains conservative: unsupported commands still delegate to the real GitHub CLI. The new behavior only activates when the repository is detected and its host is allowlisted in shim configuration.
gh pr list uses the existing Forgejo pull listing endpoint, then normalizes each pull with the same GitHub-shaped PR field mapping already used by view and status. gh repo view uses the Forgejo repository endpoint and returns a small repository field subset.
Compatibility note: search-oriented list flags such as --author, --label, and --search are accepted to avoid immediate CLI failure, but they are not yet fully implemented as Forgejo filters.
Relevant Diff Snippets
The turn document uses the Diffs SSR path from diffs.com/docs: the patch was pre-rendered with @pierre/diffs/ssr via preloadPatchDiff, so this section remains readable offline without client-side JavaScript.
27 unmodified lines2223242523 unmodified lines4243444577 unmodified lines11111211311411511411511611727 unmodified linesparse_repo_spec,)SUPPORTED_PR_COMMANDS = {"create", "new", "status", "view"}23 unmodified linescwd: str | None = None,) -> RouteDecision:if len(argv) < 2 or argv[0] != "pr" or argv[1] not in SUPPORTED_PR_COMMANDS:return RouteDecision("delegate", "unsupported command")77 unmodified linesrest = argv[2:]if command in {"create", "new"}:return _run_create(rest, repo, client, cwd=cwd, stdout=stdout, stdin=stdin)if command == "view":return _run_view(rest, repo, client, cwd=cwd, stdout=stdout)return 1def run_forgejo_pr(27 unmodified lines282930313223 unmodified lines5960616277 unmodified lines14314414514614714814911411511611711811912012112212312412512612712812913013113227 unmodified linesparse_repo_spec,)SUPPORTED_PR_COMMANDS = {"create", "list", "new", "status", "view"}SUPPORTED_REPO_COMMANDS = {"view"}23 unmodified linescwd: str | None = None,) -> RouteDecision:if len(argv) < 2 or not _is_supported_command(argv):return RouteDecision("delegate", "unsupported command")77 unmodified linesrest = argv[2:]if command in {"create", "new"}:return _run_create(rest, repo, client, cwd=cwd, stdout=stdout, stdin=stdin)if command == "list":return _run_list(rest, repo, client, stdout=stdout)if command == "view":return _run_view(rest, repo, client, cwd=cwd, stdout=stdout)return 1def run_forgejo(argv: list[str],repo: RepoRef,client: ForgejoClient,*,cwd: str | None = None,stdout: TextIO,stderr: TextIO,stdin: TextIO | None = None,) -> int:if argv[0] == "repo":return run_forgejo_repo(argv, repo, client, stdout=stdout, stderr=stderr)return run_forgejo_pr(argv, repo, client, cwd=cwd, stdout=stdout, stderr=stderr, stdin=stdin)def run_forgejo_pr(
Expected Impact for End-Users
Tools that probe repository and PR metadata through gh pr list or gh repo view should get Forgejo-backed answers instead of a GitHub remote error. In Codex.app, this should reduce false “GitHub CLI unavailable” fallbacks for supported Forgejo PR workflows.
Validation
- Ran
python3 -m unittest: 45 tests passed. - Ran the active wrapper doctor after reinstalling from this checkout: real
gh,fj, the Forgejo host, token discovery, and PATH order all reportedok. - Ran
gh pr listandgh repo viewthrough the active wrapper with an allowlisted Forgejo host and an intentionally mismatched repository path. Both commands routed to the Forgejo API and returned Forgejo 404 responses, confirming they no longer fell through to stock GitHub CLI for allowlisted hosts.
Issues, Limitations, and Mitigations
- This is still not full GitHub CLI emulation. Unsupported commands continue to delegate to real
gh. gh pr listaccepts several search flags without applying their filters yet. That keeps automation from failing early, but follow-up work should implement real filtering or explicit Forgejo API query support.- Live validation did not include a successful call against a known existing Forgejo repository because this checkout's
originis GitHub and no matching localgit.deltaisland.iocheckout was found under/Users/kell/dev. - The Diffs render is static SSR HTML generated during the turn. It does not hydrate interactivity offline, but the rendered diff content is readable without network access.
Follow-up Work
gh-forgejo-shim-9r8: implement real list filtering for--author,--label,--assignee, and--search.- Add fixture-backed tests for actual Forgejo repository payload variants if more hosts expose slightly different field names.
- Consider supporting
gh repo view --json viewerPermissionif Codex.app asks for permission checks later.