From 954446b4332dd720ebb8ab7cb736a9833846b6f1 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah Date: Sun, 19 Jul 2026 13:05:56 -0700 Subject: [PATCH] Add basectl GitHub auth refresh workflow --- cli/bash/commands/basectl/README.md | 7 +- cli/bash/commands/basectl/subcommands/gh.sh | 223 +++++++++++++++++- .../commands/basectl/tests/completions.bats | 12 +- cli/bash/commands/basectl/tests/gh.bats | 157 ++++++++++++ docs/command-reference.md | 2 + docs/github-workflow.md | 17 ++ lib/shell/completions/basectl_completion.sh | 16 +- lib/shell/completions/basectl_completion.zsh | 56 +++-- lib/shell/completions/tests/completions.bats | 6 +- 9 files changed, 475 insertions(+), 21 deletions(-) diff --git a/cli/bash/commands/basectl/README.md b/cli/bash/commands/basectl/README.md index f433183e..f77c9fe0 100644 --- a/cli/bash/commands/basectl/README.md +++ b/cli/bash/commands/basectl/README.md @@ -102,7 +102,7 @@ such command directories exist. Optional utility CLIs such as `caff` and provided, project manifest artifacts with suggested fixes. - `basectl doctor explain ` prints local, deterministic guidance for selected stable finding IDs, with optional JSON output. -- `basectl gh` manages GitHub issues, pull requests, branch naming, repository +- `basectl gh` manages GitHub authentication, issues, pull requests, branch naming, repository hygiene, and GitHub Project metadata using Base's opinionated workflow. It uses standard GitHub-style issue categories such as `bug`, `enhancement`, `documentation`, `ci`, and `security`, and derives branch names from those @@ -111,6 +111,11 @@ such command directories exist. Optional utility CLIs such as `caff` and repository from `--repo`/`-R`, then `GH_REPO`, then `origin`. Issue creation is unassigned by default unless `--assignee` is passed or `.github/base-project.yml` sets `project.issue_defaults.assignee`. + `basectl gh auth status` reports the active credential state without printing + token values. `basectl gh auth refresh --scope project` explicitly refreshes + stored credentials and requests the Project scope when needed. Refresh does + not modify `GH_TOKEN`, `GITHUB_TOKEN`, or their Enterprise variants; when one + is set, it takes precedence and must be unset or rotated at its source. Prefer this command for Base repository GitHub workflows when it supports the task. - `basectl onboard` guides first-run setup around existing setup, check, diff --git a/cli/bash/commands/basectl/subcommands/gh.sh b/cli/bash/commands/basectl/subcommands/gh.sh index 7dbc78fd..fd17f42f 100644 --- a/cli/bash/commands/basectl/subcommands/gh.sh +++ b/cli/bash/commands/basectl/subcommands/gh.sh @@ -16,6 +16,8 @@ source "$BASE_HOME/cli/bash/commands/basectl/subcommands/gh_branch_worktree.sh" base_gh_usage() { cat <<'EOF' Usage: + basectl gh auth status [--hostname ] + basectl gh auth refresh [--hostname ] [--scope ]... [--scopes ] [--clipboard] basectl gh issue list [gh options...] basectl gh issue create [--category ] --title [--body <body>] [--repo <owner/name>] [--assignee <login>|--no-assignee] [--size <T|S|M|L>] [project options...] basectl gh issue readiness <number> [--repo <owner/name>] [--project-owner <login> --project-number <number>] [--format <text|json>] @@ -54,6 +56,9 @@ Issue categories: Notes: - This command requires the GitHub CLI (`gh`) for GitHub operations. + - `auth status` reports credential state without displaying token values. + - `auth refresh` updates stored credentials only. Environment tokens such as + `GH_TOKEN` take precedence and must be rotated or unset separately. - Issues are unassigned unless --assignee is passed or .github/base-project.yml sets project.issue_defaults.assignee. - When the GitHub repo is known, issue create also adds the issue to the @@ -69,6 +74,207 @@ Notes: EOF } +base_gh_auth_usage() { + cat <<'EOF' +Usage: + basectl gh auth status [--hostname <host>] + basectl gh auth refresh [--hostname <host>] [--scope <scope>]... [--scopes <scope,...>] [--clipboard] + +Purpose: + Inspect GitHub authentication safely or refresh the stored credential for an + active account. + +Options: + --hostname <host> GitHub host. Defaults to github.com. + --scope <scope> Additional OAuth scope; repeat this option as needed. + --scopes <scope,...> Comma-separated additional OAuth scopes. + --clipboard Copy the one-time OAuth device code to the clipboard. + -h, --help Show this help text. + +Notes: + - Refresh is explicit and may open the GitHub browser/device authorization flow. + - Refresh changes stored credentials only. GH_TOKEN/GITHUB_TOKEN (or their + Enterprise variants) override stored credentials for the active process. + - If the stored credential is invalid, run `gh auth login -h <host>` first. +EOF +} + +base_gh_auth_environment_token_name() { + local hostname="${1:-github.com}" + + case "$hostname" in + github.com|*.ghe.com) + if [[ -n "${GH_TOKEN:-}" ]]; then + printf '%s\n' GH_TOKEN + return 0 + fi + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + printf '%s\n' GITHUB_TOKEN + return 0 + fi + ;; + *) + if [[ -n "${GH_ENTERPRISE_TOKEN:-}" ]]; then + printf '%s\n' GH_ENTERPRISE_TOKEN + return 0 + fi + if [[ -n "${GITHUB_ENTERPRISE_TOKEN:-}" ]]; then + printf '%s\n' GITHUB_ENTERPRISE_TOKEN + return 0 + fi + ;; + esac + + return 1 +} + +base_gh_auth_environment_warning() { + local hostname="${1:-github.com}" + local token_name + + token_name="$(base_gh_auth_environment_token_name "$hostname")" || return 1 + log_warn "GitHub CLI is using $token_name from the environment. Stored credentials and 'basectl gh auth refresh' will not affect commands until this variable is unset or rotated at its source." + return 0 +} + +base_gh_auth_status() { + local hostname="${1:-github.com}" + local output + local status=0 + + base_gh_require_command gh || return 1 + base_gh_auth_environment_warning "$hostname" || true + output="$(gh auth status --hostname "$hostname" 2>&1)" || status=$? + [[ -z "$output" ]] || printf '%s\n' "$output" + ((status == 0)) && return 0 + + if grep -Eqi 'lookup .*api\.[^[:space:]]+|error connecting|no such host|network' <<<"$output"; then + log_warn "Unable to reach GitHub while checking authentication. Verify network or DNS and retry." + elif grep -Eqi 'not logged in|invalid|failed to log in|bad credentials|401' <<<"$output"; then + log_warn "GitHub authentication is unavailable for '$hostname'. Run 'gh auth login -h $hostname'." + fi + + return "$status" +} + +base_gh_auth_status_command() { + local hostname="github.com" + + while (($#)); do + case "$1" in + --hostname) + [[ -n "${2:-}" ]] || { + base_gh_usage_error base_gh_auth_usage "Option '$1' requires a host argument." + return $? + } + hostname="$2" + shift + ;; + -h|--help) + base_gh_auth_usage + return 0 + ;; + *) + base_gh_usage_error base_gh_auth_usage "Unknown auth status option '$1'." + return $? + ;; + esac + shift + done + + base_gh_auth_status "$hostname" +} + +base_gh_auth_refresh_command() { + local hostname="github.com" + local clipboard=0 + local scope + local scopes_csv="" + local token_name + local args + local status=0 + + while (($#)); do + case "$1" in + --hostname) + [[ -n "${2:-}" ]] || { + base_gh_usage_error base_gh_auth_usage "Option '$1' requires a host argument." + return $? + } + hostname="$2" + shift + ;; + --scope) + [[ -n "${2:-}" ]] || { + base_gh_usage_error base_gh_auth_usage "Option '--scope' requires a scope argument." + return $? + } + scope="$2" + [[ -n "$scopes_csv" ]] && scopes_csv+=, + scopes_csv+="$scope" + shift + ;; + --scopes) + [[ -n "${2:-}" ]] || { + base_gh_usage_error base_gh_auth_usage "Option '--scopes' requires a scope argument." + return $? + } + [[ -n "$scopes_csv" ]] && scopes_csv+=, + scopes_csv+="$2" + shift + ;; + --clipboard) + clipboard=1 + ;; + -h|--help) + base_gh_auth_usage + return 0 + ;; + *) + base_gh_usage_error base_gh_auth_usage "Unknown auth refresh option '$1'." + return $? + ;; + esac + shift + done + + token_name="$(base_gh_auth_environment_token_name "$hostname")" || true + if [[ -n "$token_name" ]]; then + base_gh_error "Cannot refresh the stored GitHub credential while $token_name is set." + log_warn "Unset $token_name for this process or rotate the environment token at its source, then retry." + return 2 + fi + + base_gh_require_command gh || return 1 + args=(auth refresh --hostname "$hostname") + [[ -z "$scopes_csv" ]] || args+=(--scopes "$scopes_csv") + ((clipboard)) && args+=(--clipboard) + base_gh_run "${args[@]}" || status=$? + ((status == 0)) || return "$status" + printf "GitHub credentials refreshed for '%s'.\n" "$hostname" +} + +base_gh_do_auth() { + local command="${1:-}" + shift || true + + case "$command" in + status) + base_gh_auth_status_command "$@" + ;; + refresh) + base_gh_auth_refresh_command "$@" + ;; + -h|--help|help|"") + base_gh_auth_usage + ;; + *) + base_gh_usage_error base_gh_auth_usage "Unknown gh auth command '$command'." + return $? + ;; + esac +} + base_gh_issue_usage() { cat <<'EOF' Usage: @@ -443,7 +649,18 @@ base_gh_report_command_failure() { } base_gh_run() { - gh_run "$@" + local status=0 + + gh_run "$@" || status=$? + ((status == 0)) && return 0 + + base_gh_auth_environment_warning github.com || true + if [[ "${1:-}" == project ]]; then + log_warn "GitHub Project operations require the 'project' scope for stored OAuth credentials." + log_warn "Run 'basectl gh auth refresh --scope project' if the stored credential is active." + fi + + return "$status" } base_gh_args_request_help() { @@ -1349,6 +1566,7 @@ base_gh_issue_create() { printf '%s\n' "$issue_output" if ((configure_project)) && [[ -n "$github_repo" ]]; then + base_gh_auth_environment_warning github.com || true issue_number="$(base_gh_issue_number_from_output "$issue_output")" || { base_gh_error "Unable to determine created issue number from gh output." return 1 @@ -1611,6 +1829,8 @@ base_gh_do_project() { return 0 fi + base_gh_auth_environment_warning github.com || true + [[ -x "$wrapper" ]] || { base_gh_error "Base Python wrapper '$wrapper' is missing or is not executable." return 1 @@ -1623,6 +1843,7 @@ base_gh_subcommand_main() { shift || true case "$area" in + auth) base_gh_do_auth "$@" ;; issue) base_gh_do_issue "$@" ;; pr) base_gh_do_pr "$@" ;; project) base_gh_do_project "$@" ;; diff --git a/cli/bash/commands/basectl/tests/completions.bats b/cli/bash/commands/basectl/tests/completions.bats index 20b14c2b..ca99f8f9 100644 --- a/cli/bash/commands/basectl/tests/completions.bats +++ b/cli/bash/commands/basectl/tests/completions.bats @@ -269,6 +269,14 @@ EOF COMP_CWORD=2; \ _base_basectl_completion; \ printf "gh_areas=%s\n" "${COMPREPLY[*]}"; \ + COMP_WORDS=(basectl gh auth ""); \ + COMP_CWORD=3; \ + _base_basectl_completion; \ + printf "gh_auth_commands=%s\n" "${COMPREPLY[*]}"; \ + COMP_WORDS=(basectl gh auth refresh --); \ + COMP_CWORD=4; \ + _base_basectl_completion; \ + printf "gh_auth_refresh_options=%s\n" "${COMPREPLY[*]}"; \ COMP_WORDS=(basectl gh project ""); \ COMP_CWORD=3; \ _base_basectl_completion; \ @@ -351,7 +359,9 @@ EOF [[ "$output" == *"ci_check_options=--ci --profile --format --manifest --remote-network"* ]] [[ "$output" == *"ci_doctor_projects=base demo"* ]] [[ "$output" == *"ci_doctor_options=--ci --profile --format --manifest --remote-network --no-color"* ]] - [[ "$output" == *"gh_areas=issue pr branch worktree project"* ]] + [[ "$output" == *"gh_areas=auth issue pr branch worktree project"* ]] + [[ "$output" == *"gh_auth_commands=status refresh"* ]] + [[ "$output" == *"gh_auth_refresh_options=--hostname --scope --scopes --clipboard --help"* ]] [[ "$output" == *"gh_project_commands=doctor configure issue"* ]] [[ "$output" == *"gh_project_configure_options=--project --owner --schema --config --copy-fields-from --initiative-option --repo --replace-project --dry-run"* ]] [[ "$output" == *"gh_project_issue_set_fields_options=--repo --project --owner --config --status --priority --area --initiative --size --dry-run"* ]] diff --git a/cli/bash/commands/basectl/tests/gh.bats b/cli/bash/commands/basectl/tests/gh.bats index a83be996..73bb60c1 100644 --- a/cli/bash/commands/basectl/tests/gh.bats +++ b/cli/bash/commands/basectl/tests/gh.bats @@ -13,6 +13,55 @@ EOF chmod +x "$TEST_MOCKBIN/gh" } +write_auth_gh_mock() { + cat > "$TEST_MOCKBIN/gh" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$*" >> "${BASE_GH_TEST_STATE_DIR:?}/gh-calls" +case "$*" in + "auth status --hostname github.com") + printf 'Logged in to github.com account codeforester (keyring)\nToken scopes: gist, repo, project\n' + exit 0 + ;; + auth\ refresh\ --hostname\ github.com*) + printf 'OAuth refresh complete\n' + exit 0 + ;; +esac +printf 'unexpected gh args: %s\n' "$*" >&2 +exit 99 +EOF + chmod +x "$TEST_MOCKBIN/gh" +} + +write_auth_network_failure_gh_mock() { + cat > "$TEST_MOCKBIN/gh" <<'EOF' +#!/usr/bin/env bash +if [[ "$*" == "auth status --hostname github.com" ]]; then + printf 'error connecting to api.github.com: lookup api.github.com: no such host\n' >&2 + exit 1 +fi +printf 'unexpected gh args: %s\n' "$*" >&2 +exit 99 +EOF + chmod +x "$TEST_MOCKBIN/gh" +} + +write_project_scope_failure_gh_mock() { + cat > "$TEST_MOCKBIN/gh" <<'EOF' +#!/usr/bin/env bash +if [[ "$*" == "auth status -h github.com" ]]; then + exit 0 +fi +if [[ "$1" == "project" ]]; then + printf 'error: missing required scopes [read:project]\n' >&2 + exit 1 +fi +printf 'unexpected gh args: %s\n' "$*" >&2 +exit 99 +EOF + chmod +x "$TEST_MOCKBIN/gh" +} + add_github_origin() { git -C "$1" remote add origin https://github.com/basefoundry/base.git } @@ -239,6 +288,99 @@ run_gh_subcommand() { [ "$status" -eq 0 ] } +@test "basectl gh auth status reports stored credential state" { + write_auth_gh_mock + + run_gh_subcommand auth status + + [ "$status" -eq 0 ] + [[ "$output" == *"Logged in to github.com account codeforester (keyring)"* ]] + [[ "$output" == *"Token scopes: gist, repo, project"* ]] + [[ "$(cat "$TEST_STATE_DIR/gh-calls")" == "auth status --hostname github.com" ]] +} + +@test "basectl gh auth refresh combines repeatable scopes and forwards them" { + write_auth_gh_mock + + run_gh_subcommand auth refresh --scope project --scope read:org + + [ "$status" -eq 0 ] + [[ "$output" == *"GitHub credentials refreshed for 'github.com'."* ]] + [[ "$(cat "$TEST_STATE_DIR/gh-calls")" == "auth refresh --hostname github.com --scopes project,read:org" ]] +} + +@test "basectl gh auth refresh refuses to hide an environment token override" { + write_auth_gh_mock + + run env \ + HOME="$TEST_HOME" \ + BASE_HOME="$BASE_REPO_ROOT" \ + BASE_GH_TEST_CWD="$TEST_HOME" \ + BASE_GH_TEST_STATE_DIR="$TEST_STATE_DIR" \ + GH_TOKEN="test-token" \ + PATH="$TEST_MOCKBIN:$PATH" \ + bash -c ' + cd "$BASE_GH_TEST_CWD" + source "$BASE_HOME/base_init.sh" + source "$BASE_HOME/cli/bash/commands/basectl/subcommands/gh.sh" + base_gh_subcommand_main auth refresh --scope project + ' bash + + [ "$status" -eq 2 ] + [[ "$output" == *"Cannot refresh the stored GitHub credential while GH_TOKEN is set."* ]] + [ ! -f "$TEST_STATE_DIR/gh-calls" ] +} + +@test "basectl gh auth status warns when an environment token overrides storage" { + write_auth_gh_mock + + run env \ + HOME="$TEST_HOME" \ + BASE_HOME="$BASE_REPO_ROOT" \ + BASE_GH_TEST_CWD="$TEST_HOME" \ + BASE_GH_TEST_STATE_DIR="$TEST_STATE_DIR" \ + GH_TOKEN="test-token" \ + PATH="$TEST_MOCKBIN:$PATH" \ + bash -c ' + cd "$BASE_GH_TEST_CWD" + source "$BASE_HOME/base_init.sh" + source "$BASE_HOME/cli/bash/commands/basectl/subcommands/gh.sh" + base_gh_subcommand_main auth status + ' bash + + [ "$status" -eq 0 ] + [[ "$output" == *"GitHub CLI is using GH_TOKEN from the environment"* ]] +} + +@test "basectl gh auth status distinguishes network failures from auth failures" { + write_auth_network_failure_gh_mock + + run_gh_subcommand auth status + + [ "$status" -eq 1 ] + [[ "$output" == *"Unable to reach GitHub while checking authentication"* ]] + [[ "$output" != *"Run 'gh auth login"* ]] +} + +@test "basectl gh project failures suggest the auth scope refresh" { + write_project_scope_failure_gh_mock + + run env \ + HOME="$TEST_HOME" \ + BASE_HOME="$BASE_REPO_ROOT" \ + BASE_GH_TEST_STATE_DIR="$TEST_STATE_DIR" \ + PATH="$TEST_MOCKBIN:$PATH" \ + bash -c ' + source "$BASE_HOME/base_init.sh" + source "$BASE_HOME/cli/bash/commands/basectl/subcommands/gh.sh" + base_gh_run project item-list 10 + ' bash + + [ "$status" -eq 1 ] + [[ "$output" == *"missing required scopes [read:project]"* ]] + [[ "$output" == *"basectl gh auth refresh --scope project"* ]] +} + @test "basectl gh default branch and repo inference delegate to reusable gh helpers" { run env \ HOME="$TEST_HOME" \ @@ -286,6 +428,8 @@ run_gh_subcommand() { [ "$status" -eq 0 ] [[ "$output" == *"Usage:"* ]] + [[ "$output" == *"basectl gh auth status"* ]] + [[ "$output" == *"basectl gh auth refresh"* ]] [[ "$output" == *"basectl gh issue start <number>"* ]] [[ "$output" == *"basectl gh project doctor --project <title>"* ]] [[ "$output" == *"basectl gh project configure --project <title>"* ]] @@ -296,6 +440,19 @@ run_gh_subcommand() { [[ "$output" == *"sets project.issue_defaults.assignee"* ]] } +@test "basectl gh auth prints command-scoped help" { + run_basectl gh auth --help + + [ "$status" -eq 0 ] + [[ "$output" == *"basectl gh auth status [--hostname <host>]"* ]] + [[ "$output" == *"--scope <scope>"* ]] + [[ "$output" == *"GH_TOKEN/GITHUB_TOKEN"* ]] + + run_basectl gh auth refresh --help + [ "$status" -eq 0 ] + [[ "$output" == *"Additional OAuth scope"* ]] +} + @test "basectl gh issue prints area help" { run_basectl gh issue --help diff --git a/docs/command-reference.md b/docs/command-reference.md index df75ea36..a98c3830 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -150,6 +150,8 @@ daily project loop commands from the local checkout. | `basectl gh issue create` | Create an issue with Base category conventions, assign it, and add repo Project metadata when the repo is known. Defaults to `--category enhancement` and Project `Size=S` when omitted. | `--category <bug\|enhancement\|documentation\|ci\|security>`, `--title <title>`, `--body <body>`, `--repo <owner/name>`, `--project <title>`, `--project-owner <login>`, `--size <T\|S\|M\|L>`, `--no-project` | | `basectl gh issue readiness <number>` | Check whether an issue has the required body sections and, when Project coordinates are supplied, Base Project fields before assignment. Omitting Project coordinates reports a partial result. Stable inspection JSON uses the shared v1 envelope. | `--repo <owner/name>`, `--project-owner <login>`, `--project-number <number>`, `--format <text\|json>` | | `basectl gh issue start <number>` | Start the issue-backed branch workflow after verifying the issue has exactly one standard category; an explicit `--category` must match that label. The issue repository resolves from an explicit selector, then `GH_REPO`, then `origin`. | `--category <category>`, `--title <title>`, `--repo <owner/name>`, `-R <owner/name>` | +| `basectl gh auth status` | Inspect GitHub authentication state without displaying token values. Warns when an environment token takes precedence over stored credentials and distinguishes network failures from unavailable login. | `--hostname <host>` | +| `basectl gh auth refresh` | Explicitly refresh the stored GitHub credential and optionally request additional OAuth scopes. It does not alter `GH_TOKEN` or other environment-provided credentials. | `--hostname <host>`, repeatable `--scope <scope>`, `--scopes <scope,...>`, `--clipboard` | | `basectl gh pr create/status/checks/ready/merge` | Create and manage pull requests through Base's workflow wrapper. `pr create` rejects a noncanonical current branch before invoking GitHub, auto-injects `Fixes #<issue>` unless `--no-fixes` is passed, and uses `github.pr` from `base_manifest.yaml` when present. | passes through `gh` options; `pr create` also accepts `--no-fixes` | | `basectl gh branch stale` | Report stale local branches. Stable inspection JSON uses the shared v1 envelope. | `--days <days>`, `--format <text\|json>` | | `basectl gh branch prune` | Prune safe merged branches. | `--dry-run`, `--yes`, `--remote` | diff --git a/docs/github-workflow.md b/docs/github-workflow.md index 1499f206..1f30def6 100644 --- a/docs/github-workflow.md +++ b/docs/github-workflow.md @@ -203,6 +203,23 @@ basectl gh project issue set-fields 604 --repo basefoundry/base --project base - ## Preferred GitHub Interface +Use the Base auth wrapper when diagnosing or repairing local GitHub CLI access: + +```bash +basectl gh auth status +basectl gh auth refresh --scope project +``` + +`auth status` reports credential state without exposing token values and +distinguishes network failures from unavailable login. `auth refresh` updates +the stored GitHub CLI credential and can request additional scopes, but it does +not change `GH_TOKEN`, `GITHUB_TOKEN`, or the corresponding Enterprise +variables. Those environment tokens take precedence over stored credentials; +rotate or unset them at their source before refreshing the stored credential. + +Keep login and refresh explicit. Ordinary `basectl gh` commands should report +an actionable login or scope hint rather than silently starting an OAuth flow. + Use `basectl gh` as the preferred interface for Base repository GitHub workflows when it supports the operation. diff --git a/lib/shell/completions/basectl_completion.sh b/lib/shell/completions/basectl_completion.sh index 41c6b827..966f50ae 100644 --- a/lib/shell/completions/basectl_completion.sh +++ b/lib/shell/completions/basectl_completion.sh @@ -790,9 +790,23 @@ _base_basectl_completion() { ;; gh) if ((COMP_CWORD == 2)); then - _base_basectl_completion_compgen "issue pr branch worktree project" "$cur" + _base_basectl_completion_compgen "auth issue pr branch worktree project" "$cur" else case "${COMP_WORDS[2]:-}" in + auth) + if ((COMP_CWORD == 3)); then + _base_basectl_completion_compgen "status refresh" "$cur" + else + case "${COMP_WORDS[3]:-}" in + status) + _base_basectl_completion_compgen "--hostname -h --help" "$cur" + ;; + refresh) + _base_basectl_completion_compgen "--hostname --scope --scopes --clipboard -h --help" "$cur" + ;; + esac + fi + ;; issue) if ((COMP_CWORD == 3)); then _base_basectl_completion_compgen "list create start readiness" "$cur" diff --git a/lib/shell/completions/basectl_completion.zsh b/lib/shell/completions/basectl_completion.zsh index 2bab8316..f4b49a03 100644 --- a/lib/shell/completions/basectl_completion.zsh +++ b/lib/shell/completions/basectl_completion.zsh @@ -953,15 +953,39 @@ _base_basectl_completion() { ;; gh) case "${words[3]:-}" in + auth) + case "${words[4]:-}" in + status) + _arguments '2:gh area:(auth issue pr branch worktree project)' \ + '3:auth command:(status refresh)' \ + '--hostname[GitHub host]:host:' \ + '(-h --help)'{-h,--help}'[Show help text]' + ;; + refresh) + _arguments '2:gh area:(auth issue pr branch worktree project)' \ + '3:auth command:(status refresh)' \ + '--hostname[GitHub host]:host:' \ + '--scope[Additional OAuth scope]:scope:' \ + '--scopes[Comma-separated OAuth scopes]:scopes:' \ + '--clipboard[Copy the one-time OAuth device code]' \ + '(-h --help)'{-h,--help}'[Show help text]' + ;; + *) + _arguments '2:gh area:(auth issue pr branch worktree project)' \ + '3:auth command:(status refresh)' \ + '(-h --help)'{-h,--help}'[Show help text]' + ;; + esac + ;; issue) case "${words[4]:-}" in list) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:issue command:(list create start readiness)' \ '(-h --help)'{-h,--help}'[Show help text]' ;; create) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:issue command:(list create start readiness)' \ '--category[Issue category]:category:(bug enhancement documentation ci security)' \ '--title[Issue title]:title:' \ @@ -976,7 +1000,7 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; start) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:issue command:(list create start readiness)' \ '4:issue number:' \ '--category[Issue category]:category:(bug enhancement documentation ci security)' \ @@ -985,7 +1009,7 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; readiness) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:issue command:(list create start readiness)' \ '4:issue number:' \ '--repo[GitHub repository]:repo:' \ @@ -995,7 +1019,7 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; *) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:issue command:(list create start readiness)' \ '(-h --help)'{-h,--help}'[Show help text]' ;; @@ -1003,37 +1027,37 @@ _base_basectl_completion() { ;; pr) if [[ "${words[4]:-}" == create ]]; then - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:pr command:(create status checks ready merge)' \ '--no-fixes[Do not add an issue-closing line derived from the branch]' \ '(-h --help)'{-h,--help}'[Show help text]' else - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:pr command:(create status checks ready merge)' \ '(-h --help)'{-h,--help}'[Show help text]' fi ;; branch) if [[ "${words[4]:-}" == stale ]]; then - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:branch command:(stale prune)' \ '--days[Stale threshold in days]:days:' \ '--format[Output format]:format:(text json)' \ '(-h --help)'{-h,--help}'[Show help text]' elif [[ "${words[4]:-}" == prune ]]; then - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:branch command:(stale prune)' \ '--dry-run[Show planned deletions]' \ '--yes[Apply branch pruning]' \ '--remote[Prune stale remote tracking refs]' \ '(-h --help)'{-h,--help}'[Show help text]' else - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:branch command:(stale prune)' fi ;; worktree) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:worktree command:(prune)' \ '--dry-run[Show planned removals]' \ '--yes[Apply worktree pruning]' \ @@ -1042,7 +1066,7 @@ _base_basectl_completion() { project) case "${words[4]:-}" in doctor) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:project command:(doctor configure issue)' \ '--project[GitHub Project title]:title:' \ '--owner[GitHub Project owner]:owner:' \ @@ -1050,7 +1074,7 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; configure) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:project command:(doctor configure issue)' \ '--project[GitHub Project title]:title:' \ '--owner[GitHub Project owner]:owner:' \ @@ -1064,7 +1088,7 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; issue) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:project command:(doctor configure issue)' \ '4:issue command:(set-fields)' \ '5:issue number:' \ @@ -1081,13 +1105,13 @@ _base_basectl_completion() { '(-h --help)'{-h,--help}'[Show help text]' ;; *) - _arguments '2:gh area:(issue pr branch worktree project)' \ + _arguments '2:gh area:(auth issue pr branch worktree project)' \ '3:project command:(doctor configure issue)' ;; esac ;; *) - _arguments '2:gh area:(issue pr branch worktree project)' + _arguments '2:gh area:(auth issue pr branch worktree project)' ;; esac ;; diff --git a/lib/shell/completions/tests/completions.bats b/lib/shell/completions/tests/completions.bats index b0a41d1c..d91baaaf 100644 --- a/lib/shell/completions/tests/completions.bats +++ b/lib/shell/completions/tests/completions.bats @@ -507,7 +507,11 @@ run_zsh_positional_completion() { run_zsh_positional_completion basectl gh "" [ "$status" -eq 0 ] - [[ "$output" == *"positional=2:gh area:(issue pr branch worktree project)"* ]] + [[ "$output" == *"positional=2:gh area:(auth issue pr branch worktree project)"* ]] + + run_zsh_positional_completion basectl gh auth "" + [ "$status" -eq 0 ] + [[ "$output" == *"positional=3:auth command:(status refresh)"* ]] run_zsh_positional_completion basectl gh issue "" [ "$status" -eq 0 ]