# gh-axi Interface Reference This document describes the complete user-facing interface of gh-axi as implemented in the source. It is intended to be used as a conformance target for gitea-axi's implementation. All output is TOON-encoded. The TOON block type names (`pull_requests`, `issues`, `comment`, etc.) are exact — do not substitute synonyms. --- ## Table of Contents 1. [Invocation and Installation](#invocation-and-installation) 2. [Top-Level Flags](#top-level-flags) 3. [Repository and Host Targeting](#repository-and-host-targeting) 4. [Exit Codes](#exit-codes) 5. [Output Structure and TOON Encoding](#output-structure-and-toon-encoding) 6. [Count Line Format](#count-line-format) 7. [Truncation Behaviour](#truncation-behaviour) 8. [Suggestion / Help Lines](#suggestion--help-lines) 9. [Error Handling](#error-handling) 10. [Home / Dashboard](#home--dashboard) 11. [issue list](#issue-list) 12. [issue view](#issue-view) 13. [issue create](#issue-create) 14. [issue edit](#issue-edit) 15. [issue close](#issue-close) 16. [issue reopen](#issue-reopen) 17. [issue comment](#issue-comment) 18. [issue delete](#issue-delete) 19. [issue lock](#issue-lock) 20. [issue unlock](#issue-unlock) 21. [issue pin](#issue-pin) 22. [issue unpin](#issue-unpin) 23. [issue transfer](#issue-transfer) 24. [issue subissue add](#issue-subissue-add) 25. [issue subissue remove](#issue-subissue-remove) 26. [issue subissue list](#issue-subissue-list) 27. [pr list](#pr-list) 28. [pr view](#pr-view) 29. [pr create](#pr-create) 30. [pr edit](#pr-edit) 31. [pr close](#pr-close) 32. [pr merge](#pr-merge) 33. [pr review](#pr-review) 34. [pr checks](#pr-checks) 35. [pr diff](#pr-diff) 36. [pr checkout](#pr-checkout) 37. [pr ready](#pr-ready) 38. [pr reopen](#pr-reopen) 39. [pr comment](#pr-comment) 40. [pr update-branch](#pr-update-branch) 41. [pr revert](#pr-revert) 42. [label list](#label-list) 43. [label create](#label-create) 44. [label edit](#label-edit) 45. [label delete](#label-delete) --- ## Invocation and Installation ``` npx -y gh-axi [subcommand] [args] [flags] ``` gh-axi requires the `gh` CLI installed and authenticated. When `gh` is absent, the error code is `GH_NOT_INSTALLED` with the message: `gh CLI is not installed — see https://cli.github.com` --- ## Top-Level Flags These flags are processed by the SDK harness before any command runs. They are accepted anywhere in the argument list after the binary name. | Flag | Aliases | Description | |------|---------|-------------| | `--help` | `-h` | Show help for the current command | | `--version` | `-v`, `-V` | Print the installed gh-axi version | | `--repo ` | `-R `, `--repo=`, `-R=` | Target a specific repository | | `--hostname ` | `--hostname=` | Target a custom GitHub Enterprise host | The top-level help text is: ``` usage: gh-axi [command] [args] [flags] commands[14]: (none)=dashboard, issue, pr, run, workflow, release, repo, label, project, secret, variable, search, api, setup flags[4]: -R/--repo (after command), --hostname (after command) or GH_HOST env, both flags accept space or equals form, --help, -v/-V/--version examples: gh-axi gh-axi issue list --state open gh-axi issue list -R owner/name gh-axi issue list --repo=owner/name gh-axi issue list --hostname git.example.com gh-axi pr view 42 gh-axi secret list gh-axi setup hooks ``` --- ## Repository and Host Targeting ### -R / --repo flag The `-R`/`--repo` flag must appear **after** the command (not before it): ``` gh-axi issue list -R owner/name # correct gh-axi issue list --repo owner/name # correct gh-axi issue list --repo=owner/name # correct gh-axi issue list -R=owner/name # correct gh-axi -R owner/name issue list # WRONG — not accepted ``` The `search` command is the sole exception: `--repo` is passed through to the underlying `gh` call in addition to being used for context resolution. The `repo view` subcommand additionally accepts exactly one positional repository argument (`gh-axi repo view owner/name`) as a compatibility exception for `gh repo view []`. Do not combine the positional form with `--repo`. ### --hostname flag `--hostname` must also appear after the command. An explicit `--hostname` flag takes precedence over the `GH_HOST` environment variable. When `--hostname` is present, `GH_HOST` is set to its value for the lifetime of the child `gh` process. ### Suggestion -R injection rule When the current repo context was **not** inferred from the local git checkout (i.e. `ctx.source !== "git"`), suggestion lines include the repo flag. The flag is placed **after** `gh-axi` and **before** the sub-command tokens using the `-R owner/name` form. Template strings in the suggestions source use the pattern: ``` `gh-axi${repoFlag(c)} issue view ` ``` which expands to `` `gh-axi -R owner/name issue view ` `` when a non-git repo is active. Suggestion lines are then post-processed by `normalizeRepoFlagLine`, which rewrites the anti-pattern `` `gh-axi -R ` `` to `` `gh-axi -R ` `` (flag after command). When `--hostname` was supplied via flag (not env), all backtick-wrapped `gh-axi` commands in suggestions are also suffixed with ` --hostname `. --- ## Exit Codes gh-axi inherits exit code semantics from `axi-sdk-js`. Verified against `axi-sdk-js@0.1.8` source: `exitCodeForError` returns 2 for `VALIDATION_ERROR` and 1 for every other error. The mapping by error code is: | AxiError code | Exit code | |---|---| | `REPO_NOT_FOUND` | 1 | | `NOT_FOUND` | 1 | | `AUTH_REQUIRED` | 1 | | `FORBIDDEN` | 1 | | `VALIDATION_ERROR` | 2 | | `RATE_LIMITED` | 1 | | `GH_NOT_INSTALLED` | 1 | | `UNKNOWN` | 1 | | Success | 0 | --- ## Output Structure and TOON Encoding All output is TOON-encoded. The SDK function `renderOutput(blocks: string[])` joins multiple TOON blocks into a single output string. Common block types used across commands: - `renderList(name, items, schema)` — a labelled list block; the name becomes the TOON block key (e.g. `issues:`, `pull_requests:`, `labels:`) - `renderDetail(name, item, schema)` — a single-entity detail block - `renderHelp(lines)` — a `help:` block containing suggestion lines - `renderError(message, code, suggestions)` — an error block - `encode(object)` — raw TOON encoding of a plain object ### Field definitions (schema primitives) | Primitive | Description | |---|---| | `field(key)` | Outputs `key: value` verbatim | | `field(key, alias)` | Outputs `alias: value` using `key` as the data lookup | | `lower(key)` | Outputs value lowercased | | `pluck(key, subkey, alias)` | Navigates `item[key][subkey]`, outputs as `alias` | | `relativeTime(key, alias)` | Converts ISO timestamp to relative time string, outputs as `alias` | | `joinArray(key, subkey, alias)` | Joins an array of objects by extracting `subkey` from each, comma-separated | | `boolYesNo(key, alias)` | Outputs `yes` or `no` for a boolean | | `mapEnum(key, map, default, alias)` | Maps enum strings; unknown values become `default` | | `custom(alias, fn)` | Calls `fn(item)` and outputs result as `alias` | --- ## Count Line Format The `formatCountLine` function produces one of four exact phrases depending on context: | Condition | Output | |---|---| | `apiLimitHit === true` | `count: N+ (GitHub search API limit reached)` | | `totalCount` is known from GraphQL | `count: N of T total` | | `count > displayLimit` (display truncation) | `count: N (showing first D)` | | `count === limit && count > 0` (hit request limit, no GraphQL total) | `count: N (showing first N)` | | Otherwise | `count: N` | Where: - `N` = number of items returned - `T` = true total from GraphQL - `D` = display limit For `issue list` and `pr list`: when `count === limit` and a `RepoContext` is available, gh-axi makes a GraphQL query for `totalCount`. If the GraphQL call succeeds, the `count: N of T total` form is used. If it fails, it falls back to `count: N (showing first N)`. --- ## Truncation Behaviour ### Body truncation (issues and PRs) The `truncateBody(text, limit)` function truncates the body field. - Issue `view` default schema: body truncated at **500 characters**. - PR `view` default schema: body truncated at **500 characters**. - Issue `comment` result schema: body truncated at **800 characters**. When `--full` is passed to `issue view` or `pr view`, the body is output in full without truncation. The exact hint text appended when truncation occurs is not directly visible in the schema — it is returned by `truncateBody` from `body.ts` (not provided in the fetched sources). The `--full` flag is the documented way to bypass truncation. ### PR diff truncation The `pr diff` subcommand truncates at **4000 characters** by default. When truncated: - The block contains `truncated: true` and `original_length: N` - A suggestion is prepended: `` Run `gh-axi pr diff --full` to see the complete diff `` (with `-R owner/name` injected if non-git repo context) When `--full` is passed, the full diff is output. ### Workflow log truncation `run view --log` and `run view --log-failed` truncate at **20,000 characters** (tail kept). When truncated, the complete log is best-effort saved to a temp file and exposed as a `full_log` field. A `help:` hint tells agents to grep that file for earlier context. --- ## Suggestion / Help Lines Every command ends with a `help:` block produced by `renderHelp(suggestions)`. The suggestions are looked up from a static table by `(domain, action, isEmpty?, state?, id?)`. Empty suggestions produce an empty `help:` block (not omitted). ### Issue suggestions | Trigger | Suggestions | |---|---| | `issue list`, not empty | `` Run `gh-axi issue view ` to view details `` / `` Run `gh-axi issue create --title "..." --body-file ` to create `` | | `issue list`, empty | `` Run `gh-axi issue create --title "..." --body-file ` to create an issue `` / `` Run `gh-axi issue list --state closed` to see closed issues `` | | `issue view`, state=open | Comment / close / assign / search PRs suggestions | | `issue view`, state=closed | Reopen / comment / search PRs suggestions | | `issue create` | View / label suggestions with `id` filled | | `issue edit` | `` Run `gh-axi issue view ` to see updated issue `` | | `issue close` | `` Run `gh-axi issue reopen ` to reopen `` | | `issue reopen` | Close / view suggestions | | `issue comment` | `` Run `gh-axi issue view --comments` to see all comments `` | | `issue delete` | `` Run `gh-axi issue list` to see remaining issues `` | | `issue lock`, `unlock`, `pin`, `unpin` | `` Run `gh-axi issue view ` to see issue details `` | | `issue transfer` | (empty) | ### PR suggestions | Trigger | Suggestions | |---|---| | `pr list`, not empty | View / create suggestions | | `pr list`, empty | Create / closed-state suggestions | | `pr view`, state=open | Checks / approve / merge suggestions | | `pr view`, state=closed | `` Run `gh-axi pr reopen ` to reopen `` | | `pr view`, state=merged | `` Run `gh-axi pr revert ` to revert `` | | `pr create` | View / checks suggestions with `id` filled | | `pr edit` | View suggestion | | `pr close` | Reopen suggestion | | `pr merge` | Revert suggestion | | `pr review` | View suggestion | | `pr checks` | View / merge suggestions | | `pr diff` | Approve suggestion (plus truncation hint if truncated) | | `pr checkout` | (empty) | | `pr ready` | View suggestion | | `pr reopen` | View suggestion | | `pr comment` | `` Run `gh-axi pr view --comments` to see all comments `` | | `pr update-branch` | `` Run `gh-axi pr checks ` to monitor CI after update `` | | `pr revert` | `` Run `gh-axi pr view ` to see the revert PR `` | ### Label suggestions | Trigger | Suggestions | |---|---| | `label list` | `` Run `gh-axi label create --name "..." --color "..."` to create a label `` | | `label create` | `` Run `gh-axi label list` to see all labels `` | | `label edit` | `` Run `gh-axi label list` to see all labels `` | | `label delete` | `` Run `gh-axi label list` to see remaining labels `` | ### Home suggestions ``` Run `gh-axi ` — commands: issue, pr, run, release, repo, label, secret, variable` ``` Additionally, if `issues.length >= 3`, a hint is prepended: ``` Run `gh-axi issue list` for full issue list ``` If `prs.length >= 3`, a hint is prepended: ``` Run `gh-axi pr list` for full PR list ``` --- ## Error Handling ### AxiError codes gh-axi defines these error codes (string literals): ``` REPO_NOT_FOUND | NOT_FOUND | AUTH_REQUIRED | FORBIDDEN | VALIDATION_ERROR | RATE_LIMITED | GH_NOT_INSTALLED | UNKNOWN ``` ### mapGhError — pattern-matched gh stderr When a `gh` subprocess fails, `mapGhError(stderr, exitCode)` converts the raw stderr to a structured `AxiError`: | Matched pattern | Code | Message | |---|---|---| | `Could not resolve to a Repository with the name ''` | `REPO_NOT_FOUND` | `Repository "" not found` | | `Could not resolve to an? ... with the number of N` | `NOT_FOUND` | `Item #N does not exist in this repository` | | `issue N not found` (case-insensitive) | `NOT_FOUND` | `Issue #N does not exist` | | `pull request N not found` (case-insensitive) | `NOT_FOUND` | `Pull request #N does not exist` | | `release with tag "T" not found` (case-insensitive) | `NOT_FOUND` | `Release "T" not found` | | `run N not found` (case-insensitive) | `NOT_FOUND` | `Run N not found` | | `gh auth login` (appears in stderr) | `AUTH_REQUIRED` | `GitHub auth required — run \`gh auth login\` first` | | `authentication token is missing required scopes [S]` | `FORBIDDEN` | `GitHub token is missing required scope(s): S` | | `secondary rate limit` | `RATE_LIMITED` | `GitHub secondary rate limit hit — wait ~60s and retry` | | `API rate limit ... exceeded` | `RATE_LIMITED` | `GitHub API rate limit exceeded` | | `sub-issue is already a sub-issue of issue with number N` | `VALIDATION_ERROR` | `Issue is already a sub-issue of #N` | | `sub-?issue.*?(cycle\|circular)` | `VALIDATION_ERROR` | `Cannot add sub-issue: would create a cycle` | | `issue cannot be a sub-?issue of itself` | `VALIDATION_ERROR` | `An issue cannot be a sub-issue of itself` | | `HTTP 403` | `FORBIDDEN` | `Insufficient permissions for this action` | | `HTTP 422` | `VALIDATION_ERROR` | Extracted `"message"` field from JSON body, or `Validation error` | | `not found` (generic fallback, case-insensitive) | `NOT_FOUND` | First line of stderr | | (no match) | `UNKNOWN` | First line of stderr, or `gh exited with code N` | ### Validation errors (argument-level) These are thrown by command handlers before calling `gh`: | Command | Condition | Message | |---|---|---| | `issue list` | `--search` flag present | `issue list does not support --search. Use \`gh-axi search issues ""\` instead for full-text search with total counts.` | | `pr list` | `--search` flag present | `pr list does not support --search. Use \`gh-axi search prs ""\` instead for full-text search with total counts.` | | `issue create` | `--title` absent | `--title is required` | | `pr create` | `--title` absent | `--title is required` | | `issue comment` | `--body` / `--body-file` absent | (from `takeBody` with `required: true`) | | `pr comment` | `--body` / `--body-file` absent | (from `takeBody` with `required: true`) | | `issue transfer` | `--to-repo` absent | `--to-repo is required for transfer` | | `label create` | `--name` absent | `--name is required: gh-axi label create --name "..." --color "..."` | | `label create` | `--color` absent | `--color is required: gh-axi label create --name "..." --color "..."` | | `label edit` | positional name absent | `Label name is required: gh-axi label edit ` | | `label delete` | positional name absent | `Label name is required: gh-axi label delete ` | | `pr merge` | multiple method flags | `Choose only one merge method: --merge, --squash, or --rebase` | | `pr merge` | `--method` and shorthand conflict | `Choose either --method or a matching merge method shorthand, not both` | | `pr merge` | invalid `--method` value | `--method must be one of: merge, squash, rebase` | | `issue subissue add` | no `--repo` context | `Could not determine repository — pass --repo or run inside a git checkout` | | `issue subissue add` | no child numbers | `subissue add requires at least one child issue number` | | `issue subissue remove` | no `--repo` context | `Could not determine repository — pass --repo or run inside a git checkout` | | `issue subissue remove` | no child number | `subissue remove requires a child issue number` | | `issue subissue list` | no `--repo` context | `Could not determine repository — pass --repo or run inside a git checkout` | | `issue view`, `issue edit`, etc. | numeric arg missing | `Missing issue number` | | `pr view`, `pr merge`, etc. | numeric arg missing | `Missing PR number` | | issue type invalid | type name not found | `Unknown issue type "". Available types: A, B, C` | | issue type not configured | no types configured | `Issue types are not configured for this repository. Enable them in repo settings before using --type.` | ### Unknown subcommand errors | Command | Message | Code | Suggestions | |---|---|---|---| | `issue ` | `Unknown issue subcommand: ` | `VALIDATION_ERROR` | `Run \`gh-axi issue --help\` for usage` | | `issue subissue ` | `Unknown subissue subcommand: ` | `VALIDATION_ERROR` | `Run \`gh-axi issue subissue --help\` for usage` | | `pr ` | `Unknown pr subcommand: ` | `VALIDATION_ERROR` | `Run \`gh-axi pr --help\` to see available subcommands` | | `label ` | `Unknown subcommand: ` | `VALIDATION_ERROR` | `Available subcommands: list, create, edit, delete` | --- ## Home / Dashboard **Invocation:** `gh-axi` (no command) Runs `issue list` and `pr list` in parallel, each limited to 3 items. ### Output structure ``` repo: owner/name ← only when RepoContext is present issues: - number: 1 title: ... state: open author: alice prs: - number: 42 title: ... author: bob review: none help: - Run `gh-axi issue list` for full issue list ← only when issues.length >= 3 - Run `gh-axi pr list` for full PR list ← only when prs.length >= 3 - Run `gh-axi ` — commands: issue, pr, run, release, repo, label, secret, variable ``` When there are no open issues: `issues: 0 open` (raw string, not a list block). When there are no open PRs: `prs: 0 open` (raw string, not a list block). ### Issue schema (home) | Output field | Source | |---|---| | `number` | `number` | | `title` | `title` | | `state` | `state` lowercased | | `author` | `author.login` | ### PR schema (home) | Output field | Source | |---|---| | `number` | `number` | | `title` | `title` | | `author` | `author.login` | | `review` | `reviewDecision` mapped: `APPROVED`→`approved`, `CHANGES_REQUESTED`→`changes_requested`, `REVIEW_REQUIRED`→`required`, otherwise→`none` | --- ## issue list **Invocation:** `gh-axi issue list [flags]` ### Flags | Flag | Type | Default | Required | |---|---|---|---| | `--state ` | string | (not passed; gh default is `open`) | no | | `--label ` | string | — | no | | `--assignee ` | string | — | no | | `--author ` | string | — | no | | `--milestone ` | string | — | no | | `--sort ` | string | — | no | | `--limit ` | integer | `30` | no | | `--fields ` | comma-separated | — | no | | `--search` | boolean | — | forbidden (throws `VALIDATION_ERROR`) | Note: `--sort` is implemented by appending `--search sort:-desc` to the underlying `gh issue list` call. ### Default output fields TOON block name: `issues` | Output field | Source | |---|---| | `number` | `number` | | `title` | `title` | | `state` | `state` lowercased | | `author` | `author.login` | | `created` | `createdAt` as relative time | ### Extra fields via --fields | Name | Output field | Source | |---|---|---| | `body` | `body` | `body` verbatim | | `closedAt` | `closed_at` | `closedAt` as relative time | | `labels` | `labels` | `labels[].name` joined | | `milestone` | `milestone` | `milestone.title` | | `updatedAt` | `updated_at` | `updatedAt` as relative time | | `url` | `url` | `url` verbatim | ### Output structure ``` count: N [of T total | (showing first N)] issues: - number: 1 title: Fix login bug state: open author: alice created: 2 days ago help: - Run `gh-axi issue view ` to view details - Run `gh-axi issue create --title "..." --body-file ` to create ``` ### Empty state When no issues are found, `count: 0` is shown and the list block is empty. The `help:` suggestions switch to the empty-list variant: ``` help: - Run `gh-axi issue create --title "..." --body-file ` to create an issue - Run `gh-axi issue list --state closed` to see closed issues ``` ### Count line - If `count < limit`: `count: N` - If `count === limit` and GraphQL succeeds: `count: N of T total` - If `count === limit` and GraphQL fails: `count: N (showing first N)` --- ## issue view **Invocation:** `gh-axi issue view [flags]` ### Flags | Flag | Type | Default | Required | |---|---|---|---| | `--comments` | boolean | false | no | | `--full` | boolean | false | no | ### Default output fields TOON block name: `issue` | Output field | Source | Notes | |---|---|---| | `number` | `number` | | | `title` | `title` | | | `state` | `state` lowercased | | | `author` | `author.login` | | | `created` | `createdAt` as relative time | | | `type` | `issueType.name` | `"none"` if issueType absent/empty; field omitted entirely if the host does not support `issueType` in `gh issue view --json` | | `body` | `body` truncated at 500 chars | Full text when `--full` is passed | | `subissues` | GraphQL sub-issues | e.g. `["#20", "#101"]`; omitted if no sub-issues | | `parent` | GraphQL parent | e.g. `"#16"`; omitted if no parent | Sub-issue and parent fields require `RepoContext`. If the GraphQL call for sub-issue relationships fails, those fields are silently omitted. ### Comments output When `--comments` is passed, a separate `comments` list block is appended: TOON block name: `comments` | Output field | Source | |---|---| | `author` | `comments[].author.login` | | `created` | `comments[].createdAt` as relative time | | `body` | `comments[].body` truncated at 800 chars | Note: the `number` (issue) field is **not** shown in the comment list when rendered as a sub-block. --- ## issue create **Invocation:** `gh-axi issue create --title [flags]` ### Flags | Flag | Type | Default | Required | |---|---|---|---| | `--title ` | string | — | **yes** | | `--body ` | string | — | no | | `--body-file ` | path | — | no | | `--assignee ` | string | — | no | | `--label ` | string (repeatable) | — | no | | `--milestone ` | string | — | no | | `--project ` | string | — | no | | `--type ` | string | — | no | `--body` and `--body-file` are mutually exclusive alternatives (handled by `takeBody`). ### Default output fields TOON block name: `issue` | Output field | Source | |---|---| | `number` | `number` | | `title` | `title` | | `state` | `state` lowercased | | `url` | `url` | | `type` | `issueType.name` — only present when `--type` was supplied | ### Idempotency `issue create` is **not** idempotent. Each call creates a new issue regardless of title or content. ### Suggestions After creation, `id` is set to the newly created issue number: ``` help: - Run `gh-axi issue view ` to see the full issue - Run `gh-axi issue edit --add-label