A native desktop app that quietly cleans copied text and links using rules you control
README.md

gitlab-link #

Example Clipboard Transformer plugin. It rewrites copied GitLab project, planning, CI/CD, and repository URLs into Markdown links:

https://gitlab.example.com/acme/platform/widget/-/merge_requests/123
  -> [acme/platform/widget!123](https://gitlab.example.com/acme/platform/widget/-/merge_requests/123)

With a configured instance token and an http grant it fetches the real resource title or project name through the host HTTP capability:

  -> [Fix clipboard race (acme/platform/widget!123)](https://gitlab.example.com/...)

Any fetch failure — missing grant, missing token, network error, non-200 — falls back to the offline label, so the rule always produces something.

The project rule also handles configured project-page aliases:

https://gitlab.example.com/acme/platform/widget/-/pipelines
  -> [acme/platform/widget (Pipelines)](https://gitlab.example.com/...)
  -> [Widget (acme/platform/widget, Pipelines)](https://gitlab.example.com/...)

Direct MR and issue comment links retain their fragment. comment_display controls their label:

https://gitlab.example.com/acme/platform/widget/-/merge_requests/123#note_456
  -> [acme/platform/widget!123 (comment)](https://gitlab.example.com/...#note_456)

Available modes are hidden, marker (the default shown above), id (comment 456), author (comment by @user), and author-and-id (comment 456 by @user). Author modes use the Notes API in online mode and fall back to the comment ID when the author cannot be fetched.

The Markdown target retains the copied URL, including its query, fragment, and trailing slash. A specific pipeline uses its own rule and label:

https://gitlab.example.com/acme/platform/widget/-/pipelines/987?ref=main#jobs
  -> [acme/platform/widget Pipeline #987](https://gitlab.example.com/acme/platform/widget/-/pipelines/987?ref=main#jobs)

Other individual resources use concise GitLab-style references:

/-/milestones/42  -> acme/platform/widget%42
/-/jobs/654       -> acme/platform/widget Job #654
/-/commit/0123456789abcdef  -> acme/platform/widget@01234567
/-/tags/v2.1.0    -> acme/platform/widget@v2.1.0 (Tag)

The repository rule handles tree, blob, raw, blame, commits, and compare. It displays the complete locator after the kind, because branch names can contain slashes and cannot be reliably separated from a file path by looking at URL segments alone:

/-/blob/feature/topic/src/lib.rs
  -> acme/platform/widget (File: feature/topic/src/lib.rs)

/-/blob/main/src/lib.rs#L10
  -> acme/platform/widget (File: main/src/lib.rs, line 10)

/-/blob/main/src/lib.rs#L10-20
  -> acme/platform/widget (File: main/src/lib.rs, lines 10–20)

For blob, raw, and blame links with ref_type=heads or ref_type=tags, online mode asks GitLab for the longest matching ref prefix. This distinguishes a slash-bearing branch such as feature/search-v2 from the following file path. Commit-SHA locators are split locally. If a ref cannot be resolved, the plugin deliberately displays the complete locator instead of guessing.

/-/blob/feature/search-v2/docker/api.Dockerfile?ref_type=heads
  -> project (File: docker/api.Dockerfile, branch feature/search-v2)

Build and install #

Requires the wasm32-wasip1 target (rustup target add wasm32-wasip1). From this directory:

just build     # cargo build --target wasm32-wasip1 --release
just install   # build + copy gitlab_link.wasm into <config_dir>/plugins/

just install resolves the plugin directory from clipboard-transformer plugin paths. A running desktop app hot-reloads the plugin automatically; clipboard-transformer plugin list should then show it as operational.

Configuration #

Minimal (matches gitlab.com and falls back to offline labels):

rules:
  - type: dev.jag-k.gitlab/project
    id: gitlab-project
  - type: dev.jag-k.gitlab/mr
    id: gitlab-mr
    comment_display: marker
  - type: dev.jag-k.gitlab/issue
    id: gitlab-issue
    # hidden | marker | id | author | author-and-id
    comment_display: author-and-id
  - type: dev.jag-k.gitlab/milestone
    id: gitlab-milestone
  - type: dev.jag-k.gitlab/pipeline
    id: gitlab-pipeline
  - type: dev.jag-k.gitlab/job
    id: gitlab-job
  - type: dev.jag-k.gitlab/commit
    id: gitlab-commit
  - type: dev.jag-k.gitlab/tag
    id: gitlab-tag
  - type: dev.jag-k.gitlab/repository
    id: gitlab-repository

Full (real titles from a self-hosted instance):

plugins:
  dev.jag-k.gitlab:
    permissions:
      http: ["gitlab.example.com"]
      env_expansion: true
    settings:
      instances:
        - host: gitlab.example.com
          token: ${GITLAB_TOKEN}

rules:
  - type: dev.jag-k.gitlab/project
    id: gitlab-project
    # Exact paths below /-/. This map replaces the defaults when present.
    # Nested paths are supported.
    aliases:
      merge_requests: MRs
      issues: Issues
      pipelines: Pipelines
      releases: Releases
      wikis/home: Wiki
    # hosts defaults to the configured instance hosts.
    # online defaults to true; set false to force offline labels.
  - type: dev.jag-k.gitlab/mr
    id: gitlab-mr
    # hosts defaults to the configured instance hosts.
    # online defaults to true; set false to force offline labels.
  - type: dev.jag-k.gitlab/issue
    id: gitlab-issue
  - type: dev.jag-k.gitlab/milestone
    id: gitlab-milestone
  - type: dev.jag-k.gitlab/pipeline
    id: gitlab-pipeline
  - type: dev.jag-k.gitlab/job
    id: gitlab-job
  - type: dev.jag-k.gitlab/commit
    id: gitlab-commit
  - type: dev.jag-k.gitlab/tag
    id: gitlab-tag
  - type: dev.jag-k.gitlab/repository
    id: gitlab-repository
    # All are enabled by default.
    kinds: [tree, blob, raw, blame, commits, compare]

When aliases is omitted, the project rule enables common project pages: activity, analytics, issue boards, branches, CI Lint, container registry, deployments, environments, feature flags, forks, infrastructure, issues, jobs, labels, merge requests, milestones, packages, pipeline schedules, pipelines, members, releases, security dashboard, snippets, tags, and the wiki home page. An explicit empty map (aliases: {}) limits it to bare project URLs. Aliases match exact paths only, so semantic rules remain responsible for individual resources.

clipboard-transformer plugin example dev.jag-k.gitlab prints a copyable starting point; plugin doctor dev.jag-k.gitlab explains why titles are disabled when something is missing.