viby

GitLab repository integration

@viby/sdk/integrations/gitlab connects each product user through a GitLab OAuth application and implements Viby's provider-neutral repository contract. It works with GitLab.com, GitLab Dedicated, and self-managed instances that expose the v4 REST API. The host owns the OAuth application, callback route, persistence, and secret-encryption key; GitLab tokens remain behind Viby's secret store and are never returned to the browser, model, or ordinary records.

Create the OAuth application

Create a user-, group-, or instance-owned GitLab OAuth application. Configure the exact callback URL handled by viby.integrations.callback(request) and grant the api scope. api is required because the adapter creates projects, commits complete snapshots, creates branches, and manages merge requests in addition to reading repositories.

.env.local
GITLAB_CLIENT_ID=
GITLAB_CLIENT_SECRET=

GitLab's official references describe OAuth authorization-code and refresh flows and REST API authentication. Keep the client secret server-only. Configure DATABASE_URL and an independent 32-byte VIBY_SECRET_KEY when using the default durable connection stores.

Configure Viby

import { createViby } from "@viby/sdk";
import { gitlab } from "@viby/sdk/integrations/gitlab";

const viby = createViby({
  framework: "farmjs",
  model,
  integrations: {
    repository: {
      gitlab: gitlab({
        clientId: env.GITLAB_CLIENT_ID,
        clientSecret: env.GITLAB_CLIENT_SECRET,
      }),
    },
  },
});

For a self-managed instance, set only baseUrl; the adapter derives /api/v4, /oauth/authorize, /oauth/token, and /oauth/revoke from that origin:

gitlab({
  clientId: env.GITLAB_CLIENT_ID,
  clientSecret: env.GITLAB_CLIENT_SECRET,
  baseUrl: "https://gitlab.example.com",
});

apiUrl, the OAuth endpoint URLs, scopes, fetch, and source safety limits remain explicit test or gateway escape hatches. Every endpoint must stay on the configured GitLab origin, and opaque pagination cursors are rejected if they escape the configured v4 API resource.

Connect a user

const user = viby.forUser({ tenantId, userId });
const connection = await user.integrations.repository.connect("gitlab", {
  callbackUrl: "https://app.example.com/api/viby/integrations/callback",
  returnTo: "/settings/integrations",
});

if (connection.status === "authorization-required") {
  redirect(connection.url);
}

The callback verifies Viby's expiring single-use state, then the adapter exchanges the code with the original redirect URI, loads /user, and stores access/refresh tokens as encrypted opaque bytes. Refresh rotates the provider credential; disconnect calls GitLab's OAuth revoke endpoint before Viby deletes its local connection.

Import, push, and merge

The product workflow is identical to every other repository provider:

const repository = user.integrations.repository.use("gitlab");
const chat = await user.chats.import({
  source: repository.source({
    repository: { owner: "platform/frontend", name: "starter" },
    ref: { branch: "main" },
  }),
});

const version = await chat.latestVersion();
const result = await version!.push({
  using: repository,
  repository: {
    owner: "platform/frontend",
    name: "generated-app",
    createIfMissing: true,
  },
  branch: { name: "feat/dashboard", from: "main", createIfMissing: true },
  commit: { message: "feat: add generated dashboard" },
  pullRequest: { base: "main", title: "feat: add generated dashboard", draft: true },
});

Namespace discovery includes personal and group namespaces, including nested group paths. Source reads resolve a branch, tag, or commit and recursively download binary-safe blobs. Symbolic links and submodules are rejected rather than materialized outside the generated workspace.

Pushes compare the immutable version with the observed remote tree and send one GitLab Commits API transaction containing create, update, delete, and executable-mode actions. A stale expectedHead returns the provider-neutral conflict result before any write. Provider failures that race the observed head are re-read and normalized to the same conflict shape.

Merge-request creation uses GitLab's draft-title convention. Portable merge and squash map to the merge endpoint and bind the observed head SHA. GitLab rebases are asynchronous, so the adapter rejects portable method: "rebase"; a host that needs that workflow can call GitLab's rebase API through its own reviewed provider operation before requesting the merge.

Direct adapter failures are GitLabRepositoryError values with provider status and code when available. Connected Viby handles wrap them in the standard IntegrationOperationError while preserving tenant/user ownership and durable push history.