airbyte_ops_mcp.cli.gh
CLI commands for GitHub operations.
Commands:
airbyte-ops gh workflow status - Check GitHub Actions workflow status airbyte-ops gh workflow trigger - Trigger a GitHub Actions CI workflow airbyte-ops gh connector info - Get connector metadata from GitHub airbyte-ops gh connector get-version - Get connector version from GitHub airbyte-ops gh connector list - List connectors via GitHub API
CLI reference
The commands below are regenerated by poe docs-generate via cyclopts's
programmatic docs API; see docs/generate_cli.py.
airbyte-ops gh COMMAND
GitHub operations.
Commands:
airbyte-ops gh workflow
GitHub Actions workflow operations.
airbyte-ops gh workflow status
airbyte-ops gh workflow status [ARGS]
Check the status of a GitHub Actions workflow run.
Provide either --url OR all of (--owner, --repo, --run-id).
Parameters:
URL, --url: Full GitHub Actions workflow run URL (e.g., 'https://github.com/owner/repo/actions/runs/12345').OWNER, --owner: Repository owner (e.g., 'airbytehq').REPO, --repo: Repository name (e.g., 'airbyte').RUN-ID, --run-id: Workflow run ID.
airbyte-ops gh workflow trigger
airbyte-ops gh workflow trigger OWNER REPO WORKFLOW-FILE [ARGS]
Trigger a GitHub Actions CI workflow via workflow_dispatch.
This command triggers a workflow in any GitHub repository that has workflow_dispatch enabled. It resolves PR numbers to branch names automatically.
Parameters:
OWNER, --owner: Repository owner (e.g., 'airbytehq'). [required]REPO, --repo: Repository name (e.g., 'airbyte'). [required]WORKFLOW-FILE, --workflow-file: Workflow file name (e.g., 'connector-regression-test.yml'). [required]WORKFLOW-DEFINITION-REF, --workflow-definition-ref: Branch name or PR number for the workflow definition to use. If a PR number is provided, it resolves to the PR's head branch name. Defaults to 'main' if not specified.INPUTS, --inputs: Workflow inputs as a JSON string (e.g., '{"key": "value"}').WAIT, --wait, --no-wait: Wait for the workflow to complete before returning. [default: False]WAIT-SECONDS, --wait-seconds: Maximum seconds to wait for workflow completion (default: 600). [default: 600]
airbyte-ops gh connector
Connector operations via GitHub API.
airbyte-ops gh connector info
airbyte-ops gh connector info NAME REF [ARGS]
Get connector metadata from GitHub.
Parameters:
NAME, --name: Connector technical name (e.g., source-github). [required]REF, --ref: Git ref to read metadata.yaml from. [required]OWNER, --owner: Repository owner (e.g., airbytehq). [default: airbytehq]REPO, --repo: Repository name (e.g., airbyte or airbyte-enterprise). [default: airbyte]GH-TOKEN, --gh-token: GitHub API token. If omitted, usesresolve_default_github_token; public repos can fall back to unauthenticated requests.DPATH, --dpath: Evaluate this dpath expression against the parsed metadata.yaml object and print only that value (e.g., data/dockerImageTag).
airbyte-ops gh connector get-version
airbyte-ops gh connector get-version NAME REF [ARGS]
Get connector version from GitHub.
This is analogous to gh connector info --dpath data/dockerImageTag and uses
the same dpath evaluation internally.
Parameters:
NAME, --name: Connector technical name (e.g., source-github). [required]REF, --ref: Git ref to read metadata.yaml from. [required]OWNER, --owner: Repository owner (e.g., airbytehq). [default: airbytehq]REPO, --repo: Repository name (e.g., airbyte or airbyte-enterprise). [default: airbyte]GH-TOKEN, --gh-token: GitHub API token. If omitted, usesresolve_default_github_token; public repos can fall back to unauthenticated requests.
airbyte-ops gh connector list
airbyte-ops gh connector list [ARGS]
List connector names from GitHub without a local checkout.
Parameters:
MODIFIED-ONLY, --modified-only, --no-modified-only: Include only modified connectors from the provided PR. [default: False]PR, --pr: Pull request number or GitHub URL to inspect for changed connector files.OWNER, --owner: Repository owner (e.g., airbytehq). [default: airbytehq]REPO, --repo: Repository name (e.g., airbyte or airbyte-enterprise). [default: airbyte]GH-TOKEN, --gh-token: GitHub API token. If omitted, usesresolve_default_github_token.OUTPUT-FORMAT, --output-format: Output format: "lines", "csv", "json", or "json-gh-matrix". The GitHub Actions matrix format is {"connector":["source-x"]} and returns {"connector":[""]} when no connectors changed. [choices: lines, csv, json, json-gh-matrix] [default: lines]
1# Copyright (c) 2025 Airbyte, Inc., all rights reserved. 2"""CLI commands for GitHub operations. 3 4Commands: 5 airbyte-ops gh workflow status - Check GitHub Actions workflow status 6 airbyte-ops gh workflow trigger - Trigger a GitHub Actions CI workflow 7 airbyte-ops gh connector info - Get connector metadata from GitHub 8 airbyte-ops gh connector get-version - Get connector version from GitHub 9 airbyte-ops gh connector list - List connectors via GitHub API 10 11## CLI reference 12 13The commands below are regenerated by `poe docs-generate` via cyclopts's 14programmatic docs API; see `docs/generate_cli.py`. 15 16.. include:: ../../../docs/generated/cli/gh.md 17 :start-line: 2 18""" 19 20from __future__ import annotations 21 22# Hide Python-level members from the pdoc page for this module; the rendered 23# docs for this CLI group come entirely from the grafted `.. include::` in 24# the module docstring above. 25__all__: list[str] = [] 26 27import json 28import sys 29import time 30from typing import Annotated, Literal 31 32from cyclopts import Parameter 33 34from airbyte_ops_mcp.airbyte_repo.list_connectors import ( 35 format_github_actions_connector_matrix, 36 get_modified_connectors_from_github, 37) 38from airbyte_ops_mcp.airbyte_repo.utils import parse_pr_info 39from airbyte_ops_mcp.cli._base import App, app 40from airbyte_ops_mcp.cli._shared import exit_with_error, print_json 41from airbyte_ops_mcp.connector_metadata import ( 42 ConnectorMetadataDpathError, 43 ConnectorMetadataDpathNotFoundError, 44 format_metadata_dpath_value, 45 get_connector_version_from_metadata, 46 load_raw_connector_metadata_from_github, 47) 48from airbyte_ops_mcp.github_api import GitHubAPIError, resolve_default_github_token 49from airbyte_ops_mcp.mcp.github_actions import ( 50 check_ci_workflow_status, 51 trigger_ci_workflow, 52) 53 54AIRBYTE_REPO_OWNER = "airbytehq" 55AIRBYTE_REPO_NAME = "airbyte" 56 57# Create the gh sub-app 58gh_app = App(name="gh", help="GitHub operations.") 59app.command(gh_app) 60 61# Create the workflow sub-app under gh 62workflow_app = App(name="workflow", help="GitHub Actions workflow operations.") 63gh_app.command(workflow_app) 64 65# Create the connector sub-app under gh 66connector_app = App(name="connector", help="Connector operations via GitHub API.") 67gh_app.command(connector_app) 68 69 70ConnectorListOutputFormat = Literal["lines", "csv", "json", "json-gh-matrix"] 71 72 73def _parse_pr_details(pr: str) -> tuple[int, str | None, str | None]: 74 """Parse pull request details from a number or GitHub PR URL.""" 75 pr_number, pr_owner, pr_repo = parse_pr_info(pr) 76 if pr_number is None: 77 exit_with_error( 78 "PR must be a pull request number or GitHub URL like " 79 "https://github.com/airbytehq/airbyte/pull/123." 80 ) 81 return pr_number, pr_owner, pr_repo 82 83 84def _print_connector_list( 85 connectors: list[str], 86 output_format: ConnectorListOutputFormat, 87) -> None: 88 """Print connector names in the requested format.""" 89 if output_format == "lines": 90 for connector in connectors: 91 sys.stdout.write(connector + "\n") 92 elif output_format == "csv": 93 sys.stdout.write(",".join(connectors) + "\n") 94 elif output_format == "json": 95 sys.stdout.write(json.dumps(connectors, separators=(",", ":")) + "\n") 96 elif output_format == "json-gh-matrix": 97 sys.stdout.write( 98 json.dumps( 99 format_github_actions_connector_matrix(connectors), 100 separators=(",", ":"), 101 ) 102 + "\n" 103 ) 104 105 106@connector_app.command(name="info") 107def connector_info( 108 name: Annotated[ 109 str, 110 Parameter(help="Connector technical name (e.g., source-github)."), 111 ], 112 ref: Annotated[ 113 str, 114 Parameter(help="Git ref to read metadata.yaml from."), 115 ], 116 owner: Annotated[ 117 str, 118 Parameter(help="Repository owner (e.g., airbytehq)."), 119 ] = AIRBYTE_REPO_OWNER, 120 repo: Annotated[ 121 str, 122 Parameter(help="Repository name (e.g., airbyte or airbyte-enterprise)."), 123 ] = AIRBYTE_REPO_NAME, 124 gh_token: Annotated[ 125 str | None, 126 Parameter( 127 help=( 128 "GitHub API token. If omitted, uses `resolve_default_github_token`; " 129 "public repos can fall back to unauthenticated requests." 130 ) 131 ), 132 ] = None, 133 dpath_expression: Annotated[ 134 str | None, 135 Parameter( 136 name="--dpath", 137 help=( 138 "Evaluate this dpath expression against the parsed metadata.yaml " 139 "object and print only that value (e.g., data/dockerImageTag)." 140 ), 141 ), 142 ] = None, 143) -> None: 144 """Get connector metadata from GitHub.""" 145 try: 146 value = load_raw_connector_metadata_from_github( 147 name, 148 owner=owner, 149 repo=repo, 150 ref=ref, 151 gh_token=gh_token or resolve_default_github_token(allow_none=True), 152 dpath_expression=dpath_expression, 153 ) 154 except ( 155 ConnectorMetadataDpathError, 156 ConnectorMetadataDpathNotFoundError, 157 FileNotFoundError, 158 ValueError, 159 ) as e: 160 exit_with_error(str(e)) 161 162 if dpath_expression is None: 163 print_json(value) 164 return 165 166 sys.stdout.write(format_metadata_dpath_value(value) + "\n") 167 168 169@connector_app.command(name="get-version") 170def connector_get_version( 171 name: Annotated[ 172 str, 173 Parameter(help="Connector technical name (e.g., source-github)."), 174 ], 175 ref: Annotated[ 176 str, 177 Parameter(help="Git ref to read metadata.yaml from."), 178 ], 179 owner: Annotated[ 180 str, 181 Parameter(help="Repository owner (e.g., airbytehq)."), 182 ] = AIRBYTE_REPO_OWNER, 183 repo: Annotated[ 184 str, 185 Parameter(help="Repository name (e.g., airbyte or airbyte-enterprise)."), 186 ] = AIRBYTE_REPO_NAME, 187 gh_token: Annotated[ 188 str | None, 189 Parameter( 190 help=( 191 "GitHub API token. If omitted, uses `resolve_default_github_token`; " 192 "public repos can fall back to unauthenticated requests." 193 ) 194 ), 195 ] = None, 196) -> None: 197 """Get connector version from GitHub. 198 199 This is analogous to `gh connector info --dpath data/dockerImageTag` and uses 200 the same dpath evaluation internally. 201 """ 202 try: 203 metadata = load_raw_connector_metadata_from_github( 204 name, 205 owner=owner, 206 repo=repo, 207 ref=ref, 208 gh_token=gh_token or resolve_default_github_token(allow_none=True), 209 ) 210 version = get_connector_version_from_metadata(metadata) 211 except ( 212 ConnectorMetadataDpathError, 213 ConnectorMetadataDpathNotFoundError, 214 FileNotFoundError, 215 ValueError, 216 ) as e: 217 exit_with_error(str(e)) 218 sys.stdout.write(version + "\n") 219 220 221@connector_app.command(name="list") 222def connector_list( 223 modified_only: Annotated[ 224 bool, 225 Parameter(help="Include only modified connectors from the provided PR."), 226 ] = False, 227 pr: Annotated[ 228 str | None, 229 Parameter( 230 help="Pull request number or GitHub URL to inspect for changed connector files." 231 ), 232 ] = None, 233 owner: Annotated[ 234 str, 235 Parameter(help="Repository owner (e.g., airbytehq)."), 236 ] = AIRBYTE_REPO_OWNER, 237 repo: Annotated[ 238 str, 239 Parameter(help="Repository name (e.g., airbyte or airbyte-enterprise)."), 240 ] = AIRBYTE_REPO_NAME, 241 gh_token: Annotated[ 242 str | None, 243 Parameter( 244 help=("GitHub API token. If omitted, uses `resolve_default_github_token`.") 245 ), 246 ] = None, 247 output_format: Annotated[ 248 ConnectorListOutputFormat, 249 Parameter( 250 help=( 251 'Output format: "lines", "csv", "json", or "json-gh-matrix". ' 252 'The GitHub Actions matrix format is {"connector":["source-x"]} ' 253 'and returns {"connector":[""]} when no connectors changed.' 254 ) 255 ), 256 ] = "lines", 257) -> None: 258 """List connector names from GitHub without a local checkout.""" 259 if not modified_only: 260 exit_with_error("`--modified-only` is required for GitHub connector listing.") 261 if pr is None: 262 exit_with_error("`--pr` is required when `--modified-only` is set.") 263 264 pr_number, pr_owner, pr_repo = _parse_pr_details(pr) 265 try: 266 connectors = get_modified_connectors_from_github( 267 pr_number=pr_number, 268 pr_owner=pr_owner or owner, 269 pr_repo=pr_repo or repo, 270 gh_token=gh_token or resolve_default_github_token(), 271 ) 272 except (GitHubAPIError, ValueError) as e: 273 exit_with_error(str(e)) 274 _print_connector_list(connectors, output_format) 275 276 277@workflow_app.command(name="status") 278def workflow_status( 279 url: Annotated[ 280 str | None, 281 Parameter( 282 help="Full GitHub Actions workflow run URL " 283 "(e.g., 'https://github.com/owner/repo/actions/runs/12345')." 284 ), 285 ] = None, 286 owner: Annotated[ 287 str | None, 288 Parameter(help="Repository owner (e.g., 'airbytehq')."), 289 ] = None, 290 repo: Annotated[ 291 str | None, 292 Parameter(help="Repository name (e.g., 'airbyte')."), 293 ] = None, 294 run_id: Annotated[ 295 int | None, 296 Parameter(help="Workflow run ID."), 297 ] = None, 298) -> None: 299 """Check the status of a GitHub Actions workflow run. 300 301 Provide either --url OR all of (--owner, --repo, --run-id). 302 """ 303 # Validate input parameters 304 if url: 305 if owner or repo or run_id: 306 exit_with_error( 307 "Cannot specify --url together with --owner/--repo/--run-id. " 308 "Use either --url OR the component parts." 309 ) 310 elif not (owner and repo and run_id): 311 exit_with_error( 312 "Must provide either --url OR all of (--owner, --repo, --run-id)." 313 ) 314 315 result = check_ci_workflow_status( 316 workflow_url=url, 317 owner=owner, 318 repo=repo, 319 run_id=run_id, 320 ) 321 print_json(result.model_dump()) 322 323 324@workflow_app.command(name="trigger") 325def workflow_trigger( 326 owner: Annotated[ 327 str, 328 Parameter(help="Repository owner (e.g., 'airbytehq')."), 329 ], 330 repo: Annotated[ 331 str, 332 Parameter(help="Repository name (e.g., 'airbyte')."), 333 ], 334 workflow_file: Annotated[ 335 str, 336 Parameter(help="Workflow file name (e.g., 'connector-regression-test.yml')."), 337 ], 338 workflow_definition_ref: Annotated[ 339 str | None, 340 Parameter( 341 help="Branch name or PR number for the workflow definition to use. " 342 "If a PR number is provided, it resolves to the PR's head branch name. " 343 "Defaults to 'main' if not specified." 344 ), 345 ] = None, 346 inputs: Annotated[ 347 str | None, 348 Parameter( 349 help='Workflow inputs as a JSON string (e.g., \'{"key": "value"}\').' 350 ), 351 ] = None, 352 wait: Annotated[ 353 bool, 354 Parameter(help="Wait for the workflow to complete before returning."), 355 ] = False, 356 wait_seconds: Annotated[ 357 int, 358 Parameter( 359 help="Maximum seconds to wait for workflow completion (default: 600)." 360 ), 361 ] = 600, 362) -> None: 363 """Trigger a GitHub Actions CI workflow via workflow_dispatch. 364 365 This command triggers a workflow in any GitHub repository that has workflow_dispatch 366 enabled. It resolves PR numbers to branch names automatically. 367 """ 368 # Parse inputs JSON if provided 369 parsed_inputs: dict[str, str] | None = None 370 if inputs: 371 try: 372 parsed_inputs = json.loads(inputs) 373 except json.JSONDecodeError as e: 374 exit_with_error(f"Invalid JSON for --inputs: {e}") 375 376 # Trigger the workflow 377 result = trigger_ci_workflow( 378 owner=owner, 379 repo=repo, 380 workflow_file=workflow_file, 381 workflow_definition_ref=workflow_definition_ref, 382 inputs=parsed_inputs, 383 ) 384 385 print_json(result.model_dump()) 386 387 # If wait is enabled and we have a run_id, poll for completion 388 if wait and result.run_id: 389 print(f"\nWaiting for workflow to complete (timeout: {wait_seconds}s)...") 390 start_time = time.time() 391 poll_interval = 10 # seconds 392 393 while time.time() - start_time < wait_seconds: 394 status_result = check_ci_workflow_status( 395 owner=owner, 396 repo=repo, 397 run_id=result.run_id, 398 ) 399 400 if status_result.status == "completed": 401 print( 402 f"\nWorkflow completed with conclusion: {status_result.conclusion}" 403 ) 404 print_json(status_result.model_dump()) 405 return 406 407 elapsed = int(time.time() - start_time) 408 print(f" Status: {status_result.status} (elapsed: {elapsed}s)") 409 time.sleep(poll_interval) 410 411 print(f"\nTimeout reached after {wait_seconds}s. Workflow still running.") 412 # Print final status 413 final_status = check_ci_workflow_status( 414 owner=owner, 415 repo=repo, 416 run_id=result.run_id, 417 ) 418 print_json(final_status.model_dump())