Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/qodev_gitlab_cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# ---------------------------------------------------------------------------
# Import and register command groups
# ---------------------------------------------------------------------------
from qodev_gitlab_cli.commands.install import install_app # noqa: E402
from qodev_gitlab_cli.commands.issues import issues_app # noqa: E402
from qodev_gitlab_cli.commands.jobs import jobs_app # noqa: E402
from qodev_gitlab_cli.commands.mrs import mrs_app # noqa: E402
Expand All @@ -36,6 +37,8 @@
app.command(_sub)
_sub.help_epilogue = "" # prevent epilogue from propagating to sub-command help

app.command(install_app)

from qodev_gitlab_cli.help_reference import build_command_reference # noqa: E402

app.help_epilogue = build_command_reference(_sub_apps)
Expand Down
59 changes: 59 additions & 0 deletions src/qodev_gitlab_cli/commands/install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Install CLI resources (skills for AI agents)."""

from __future__ import annotations

import shutil
from importlib.abc import Traversable
from importlib.resources import files
from pathlib import Path
from typing import Annotated

from cyclopts import App, Parameter

from qodev_gitlab_cli.output import console

install_app = App(name="install", help="Install CLI resources.")


def _install_skills(target_root: Path | None = None) -> Path:
"""Copy bundled skill files to .claude/skills/qodev-gitlab/."""
root = target_root or Path.cwd()
dest = root / ".claude" / "skills" / "qodev-gitlab"

source = files("qodev_gitlab_cli") / "skills"

if dest.exists():
console.print(f"Replacing existing skills at {dest}")
shutil.rmtree(dest)
dest.mkdir(parents=True)

_copy_traversable(source, dest)
return dest


def _copy_traversable(source: Traversable, dest: Path) -> None:
"""Recursively copy from a Traversable (importlib.resources) to a Path."""
for item in source.iterdir():
if item.name.startswith("__"):
continue
target = dest / item.name
if item.is_file():
target.write_bytes(item.read_bytes())
elif item.is_dir():
target.mkdir(exist_ok=True)
_copy_traversable(item, target)


@install_app.default
def install(
*,
skills: Annotated[bool, Parameter(name="--skills", help="Install AI agent skill files", negative="")] = False,
) -> None:
"""Install CLI resources into the current workspace."""
if not skills:
from qodev_gitlab_cli.output import error

error("No install target specified. Use: qodev-gitlab install --skills", code="validation", exit_code=83)

dest = _install_skills()
console.print(f"[green]Installed skills to {dest}[/green]")
147 changes: 147 additions & 0 deletions src/qodev_gitlab_cli/skills/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# qodev-gitlab CLI

Agent-friendly CLI for the GitLab API. Designed for AI coding agents with structured JSON output and predictable exit codes.

## Setup

```bash
pip install qodev-gitlab-cli
export GITLAB_TOKEN="glpat-..."

# Install skill files into the current workspace
qodev-gitlab install --skills
```

The CLI auto-detects the current GitLab project from the git remote. Override with `--project GROUP/NAME` or `-p GROUP/NAME`.

## Global Options

| Flag | Description |
|------|-------------|
| `--json` | Output as JSON (default: rich Markdown) |
| `--project`, `-p` | Project ID or path (default: auto-detected) |
| `--limit` | Results per page (default: 25) |
| `--page` | Page number (default: 1) |
| `--token` | GitLab token (overrides GITLAB_TOKEN) |
| `--url` | GitLab URL (overrides GITLAB_URL) |

## Command Reference

### projects

| Command | Description |
|---------|-------------|
| `projects list [--owned]` | List projects |
| `projects get [ID]` | Get project details (default: current) |

### mrs (Merge Requests)

| Command | Description |
|---------|-------------|
| `mrs list [--state STATE]` | List MRs (default: opened) |
| `mrs get IID` | Get MR details |
| `mrs create --title TITLE [--source BRANCH] [--target BRANCH] [--description TEXT] [--labels L] [--squash]` | Create MR |
| `mrs update IID [--title T] [--description D] [--labels L] [--target B]` | Update MR |
| `mrs merge IID [--squash] [--when-pipeline-succeeds]` | Merge MR |
| `mrs close IID` | Close MR |
| `mrs discussions IID` | List MR discussions |
| `mrs changes IID` | Show MR diff |
| `mrs commits IID` | List MR commits |
| `mrs approvals IID` | Show approval status |
| `mrs comment IID --body TEXT` | Comment on MR |
| `mrs pipelines IID` | List MR pipelines |

### pipelines

| Command | Description |
|---------|-------------|
| `pipelines list [--ref BRANCH] [--limit N]` | List pipelines |
| `pipelines get ID` | Get pipeline details |
| `pipelines jobs ID` | List pipeline jobs |
| `pipelines wait ID [--timeout S] [--interval S]` | Wait for pipeline to complete |

### jobs

| Command | Description |
|---------|-------------|
| `jobs get ID` | Get job details |
| `jobs log ID` | Get job log output |
| `jobs retry ID` | Retry a failed job |

### issues

| Command | Description |
|---------|-------------|
| `issues list [--state STATE] [--labels L] [--milestone M]` | List issues |
| `issues get IID` | Get issue details |
| `issues create --title TITLE [--description D] [--labels L]` | Create issue |
| `issues update IID [--title T] [--description D] [--labels L]` | Update issue |
| `issues close IID` | Close issue |
| `issues comment IID --body TEXT` | Comment on issue |
| `issues notes IID` | List issue comments |

### releases

| Command | Description |
|---------|-------------|
| `releases list` | List releases |
| `releases get TAG` | Get release details |
| `releases create --tag TAG [--name N] [--description D] [--ref REF]` | Create release |

### variables

| Command | Description |
|---------|-------------|
| `variables list` | List CI/CD variables (values hidden) |
| `variables get KEY` | Get a CI/CD variable |
| `variables set KEY VALUE [--protected] [--masked]` | Set a CI/CD variable |

## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 80 | Authentication error (bad/missing token) |
| 81 | Not found |
| 82 | API error |
| 83 | Validation error |
| 84 | Configuration error |

## JSON Output

All commands support `--json` for structured output. Lists return:

```json
{"items": [...], "total": 10, "page": 1, "limit": 25}
```

Single resources return the raw API object. Errors return:

```json
{"error": "message", "code": "error_code"}
```

## Common Patterns

```bash
# Get current project info
qodev-gitlab projects get

# Create MR from current branch
qodev-gitlab mrs create --title "feat: add feature"

# Check pipeline status as JSON
qodev-gitlab --json pipelines list --limit 5

# Wait for pipeline then check result
qodev-gitlab pipelines wait 12345 --timeout 600

# Review MR discussions
qodev-gitlab mrs discussions 42
```

## References

For detailed workflow patterns, see:
- [MR Workflows](references/mr-workflows.md) — Create, review, and merge MRs
- [Pipeline Monitoring](references/pipeline-monitoring.md) — CI/CD monitoring patterns
Empty file.
Empty file.
100 changes: 100 additions & 0 deletions src/qodev_gitlab_cli/skills/references/mr-workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Merge Request Workflows

## Create and Submit MR

```bash
# Create MR from current branch targeting main
qodev-gitlab mrs create --title "feat: add user auth"

# Create MR with full details
qodev-gitlab mrs create \
--title "fix: resolve login timeout" \
--source feature-branch \
--target main \
--description "Fixes #42. Increases timeout from 5s to 30s." \
--labels "bug,priority::high"

# Create MR with squash enabled
qodev-gitlab mrs create --title "refactor: clean up utils" --squash
```

## Review MR

```bash
# Get MR overview
qodev-gitlab mrs get 42

# Check what changed
qodev-gitlab mrs changes 42

# Read discussions/review comments
qodev-gitlab mrs discussions 42

# Check approval status
qodev-gitlab mrs approvals 42

# Check associated pipelines
qodev-gitlab mrs pipelines 42
```

## Update and Respond

```bash
# Update MR title or description
qodev-gitlab mrs update 42 --title "feat: improved title"
qodev-gitlab mrs update 42 --description "Updated description with more context"

# Add labels
qodev-gitlab mrs update 42 --labels "reviewed,ready-to-merge"

# Leave a comment
qodev-gitlab mrs comment 42 --body "Addressed all review comments"
```

## Merge

```bash
# Merge immediately
qodev-gitlab mrs merge 42

# Squash and merge
qodev-gitlab mrs merge 42 --squash

# Merge when pipeline succeeds
qodev-gitlab mrs merge 42 --when-pipeline-succeeds
```

## Full Lifecycle Example

```bash
# 1. Create MR
qodev-gitlab mrs create --title "feat: add caching layer" --labels "enhancement"

# 2. Check pipeline status
qodev-gitlab mrs pipelines 1

# 3. Review feedback
qodev-gitlab mrs discussions 1

# 4. Address feedback and comment
qodev-gitlab mrs comment 1 --body "Fixed the race condition in cache invalidation"

# 5. Check approvals
qodev-gitlab mrs approvals 1

# 6. Merge when pipeline passes
qodev-gitlab mrs merge 1 --when-pipeline-succeeds
```

## JSON Workflows (for Automation)

```bash
# Get MR state for conditional logic
STATE=$(qodev-gitlab --json mrs get 42 | jq -r '.state')

# List all open MRs as JSON
qodev-gitlab --json mrs list --state opened

# Check if MR has conflicts
qodev-gitlab --json mrs get 42 | jq '.has_conflicts'
```
Loading