Metadata-Version: 2.5
Name: runspec-gitlab
Version: 0.3.0
Summary: GitLab runnable for runspec — read and update issues, merge requests, pipelines, and repository files, ported from the GitLab MCP server
Project-URL: Documentation, https://runspec.app/
Project-URL: Source, https://github.com/jasonfinestone/runspec/tree/main/packages/python/runspec-gitlab
Project-URL: Changelog, https://github.com/jasonfinestone/runspec/blob/main/packages/python/runspec-gitlab/CHANGELOG.md
Project-URL: Issues, https://github.com/jasonfinestone/runspec/issues
Keywords: gitlab,mcp,runnable,runspec
Requires-Python: >=3.10
Requires-Dist: runspec-gitlab-core>=0.3.0
Requires-Dist: runspec>=0.44.0
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff==0.15.20; extra == 'dev'
Description-Content-Type: text/markdown

# runspec-gitlab

A GitLab runnable for [runspec](https://pypi.org/project/runspec/), ported onto
the GitLab REST API (v4) — **not** proxying GitLab's hosted MCP server. One
runnable — **`gitlab`** — with 17 read verbs (`autonomy = "autonomous"`), 15
write verbs (`autonomy = "confirm"`, so an agent asks before each one) and 4
delete verbs (`autonomy = "manual"`, so only a person can run them). `pip install` it into a venv and it's discoverable by `runspec
local`, `runspec serve` (MCP), and runspec-console.

`runspec serve` flattens each verb into a leaf MCP tool (`gitlab_get-issue`, …).
Verb names are reconciled against GitLab's canonical MCP tool registry; the
extras (`whoami`, `get-project`, `search-projects`, `list-issues`, `get-file`,
`list-tree`) are useful REST reads GitLab has no MCP tool for.

## Install

```
pip install runspec-gitlab
```

## Verbs

| Area | Verbs |
|---|---|
| Account / project | `whoami`, `get-project`, `search-projects`, `get-file`, `list-tree` |
| Issues | `list-issues`, `get-issue` |
| Merge requests | `list-merge-requests`, `get-merge-request`, `get-merge-request-diffs`, `get-merge-request-commits`, `get-merge-request-notes`, `get-merge-request-pipelines`, `get-merge-request-conflicts` |
| Pipelines | `list-pipelines`, `get-pipeline-jobs`, `get-job-log` |

Writes (all `confirm`):

| Area | Verbs |
|---|---|
| Issues | `create-issue`, `update-issue` (edit, labels, assignees, close/reopen), `add-issue-note` |
| Merge requests | `create-merge-request`, `update-merge-request` (edit, labels, assignees, reviewers, close/reopen), `add-merge-request-note`, `merge-merge-request` |
| Repository | `create-branch`, `write-file` (create or replace a text file in one commit) |
| Pipelines / jobs | `create-pipeline`, `retry-pipeline`, `cancel-pipeline`, `retry-job`, `cancel-job`, `play-job` |

Assignees and reviewers are given as usernames. Labels, assignees, reviewers and
pipeline variables (`KEY=VALUE`) are repeatable args. Writes need a token with
the `api` scope; reads work with `read_api`.

Deletes (all `manual` — refused to an agent, run from the command line or the
console's Forms tab): `delete-issue`, `delete-branch`, `delete-file`,
`delete-pipeline`. GitLab itself limits issue and pipeline deletes to project
owners/admins and refuses protected or default branches.

`get-merge-request-conflicts` is best-effort — GitLab has no public `/api/v4`
conflicts endpoint, so it relays the web-UI endpoint and returns its JSON
verbatim.

## Auth — env vars only

```
export GITLAB_URL=https://gitlab.example.com        # default https://gitlab.com
export GITLAB_TOKEN=<personal-or-project-access-token>
export GITLAB_PROJECT=group/subgroup/project        # optional default
```

Every verb takes `--env`, selecting `GITLAB_<ENV>_*` prefixed vars. `--project`
accepts a numeric id or a `group/project` path and falls back to
`GITLAB_PROJECT`.

```
gitlab whoami
gitlab list-merge-requests --project group/app --state opened
gitlab get-job-log --project group/app --job-id 998877
gitlab create-issue --project group/app --title "Login fails" --label bug --assignee alice
gitlab write-file --project group/app --branch fix --start-branch main \
  --file-path docs/notes.md --content-file notes.md --commit-message "Add notes"
```

## Public Python API

The pure logic is in **[`runspec-gitlab-core`](../runspec-gitlab-core)** —
`httpx`-only, no `runspec` dependency, no entry points. Every op takes a
`GitlabConfig` first arg and raises `GitlabError`, so a private wrapper can
inject credentials and bake in a default `project` / `env`:

```python
from runspec_gitlab_core import config_from_env, get_issue, list_merge_requests

cfg = config_from_env(env="prod")
mrs = list_merge_requests(cfg, state="opened")
```

## Development

```
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
ruff check . && ruff format --check .
pytest
```
