Настройка CloneKit в CI с помощью origin repo clone-fast
Origin сейчас доступен в стадии ранней беты. Вы можете создавать репозитории, выполнять push и pull через git, настраивать зеркало из GitHub, просматривать и искать по коду, открывать и мержить pull request'ы, а также делиться с вашей командой Cursor.
Присылайте любые отзывы на hi@cursor.com — это поможет нам сделать продукт лучше.
origin repo clone-fast заменяет git clone в CI-задачах. Вместо клонирования с нуля команда загружает заранее подготовленный набор для клонирования — упакованный snapshot репозитория, который Cursor собирает для каждого репозитория с включённым CloneKit, — а затем догружает только те объекты, которые появились после сборки набора. На этой странице показано, как передать на runner короткоживущий токен Origin и запустить команду в Buildkite, GitHub Actions и других CI-системах.
Как это работает
Каждый запуск origin repo clone-fast {owner}/{repo} [DIR] выполняет следующее:
- Получает манифест kit из Origin, используя токен из
CURSOR_AUTH_TOKEN. В манифесте перечислены артефакты kit и коммит на его tip. - Загружает pack-артефакты с
gitcdn.origin.cursor.comи проверяет их размеры. С флагом--verifyпо ходу загрузки проверяются также их хеши. - Устанавливает их в каталог назначения, который должен быть пустым.
- Дополняет клон: получает объекты, добавленные после сборки kit, и переключает рабочую копию на текущий tip ветки по умолчанию. С флагом
--no-top-upрабочая копия остаётся на tip из kit. Флаг--bareсоздаёт голый репозиторий без working tree.
Если путь через kit срабатывает, команда выводит mode: clone-kit в stdout. Если какой-либо шаг завершается ошибкой, то при значении по умолчанию --fallback auto вместо него выполняется обычный git clone и выводится mode: git-clone. Укажите --fallback never, чтобы вместо этого завершить работу с кодом 1.
Токен и рабочая копия ведут себя одинаково в любой системе CI:
- Срок действия токенов — не более 15 минут, и обновить их нельзя. Выпускайте токен непосредственно перед клонированием.
- CLI читает токен из
CURSOR_AUTH_TOKEN. Экспортируйте его в shell, где выполняетсяorigin. - Клонирование завершается на tip ветки по умолчанию. Чтобы собрать конкретный коммит, выполните затем
git fetch origin SHAиgit checkout SHA. Origin отдаёт любой достижимый коммит по SHA.
Prerequisites
CloneKit доступен на тарифах Enterprise и включается отдельно для каждого репозитория; переключателя для самостоятельного включения нет. Обратитесь к команде вашего аккаунта Cursor, чтобы включить его для каждого клонируемого репозитория. Команда origin repo clone-fast --help выводит список доступных опций.
- Origin CLI на runner. Установите его командой
curl -fsSL https://downloads.cursor.com/origin/install.sh | sh— она размещает исполняемый файл в$HOME/.local/bin/origin. Runner на Linux требуют glibc на x64 или arm64; Alpine и другие образы на musl не поддерживаются. Runner на macOS поддерживаются. См. Install the Origin CLI. - bash, git, jq, curl 7.55 или новее и openssl 1.1.1 или новее.
clone-fastиспользуетcurlдля загрузок. - Сетевой egress к следующим хостам:
| Хост | Для чего используется |
|---|---|
downloads.cursor.com | Установка CLI |
origin.cursor.com | Манифест kit и git operations |
gitcdn.origin.cursor.com | Загрузка kit |
api.cursor.com | Выпуск токенов и запросы синхронизации mirror от Origin App |
Выберите путь
| CI-система | Откуда берётся токен | Раздел |
|---|---|---|
| Buildkite, hosted или self-hosted агенты, с Origin, подключённым как repository provider | Buildkite Agent API, в хуке checkout | Buildkite |
| GitHub Actions | Ваш Origin App, в шаге workflow | GitHub Actions |
| GitLab CI, Jenkins, CircleCI и self-hosted fleet | Ваш Origin App, в задаче | Другие CI-системы |
Buildkite
Buildkite выпускает токен за вас. Замените стандартный git checkout агента на хук checkout, который запрашивает токен у Buildkite Agent API и выполняет origin repo clone-fast.
Чтобы подключить Origin, нужно быть администратором организации в Buildkite, а чтобы установить приложение Buildkite — администратором Origin.
Подключите Origin к Buildkite
В Buildkite выберите Settings > Repository Providers > Add Provider > Origin или нажмите Connect Origin account на странице New Pipeline. Выберите владельца и репозитории, затем установите приложение Buildkite. Buildkite запрашивает доступ на чтение содержимого репозитория и pull request'ов, а также доступ на чтение и запись к проверкам. Подробнее см. Origin в документации Buildkite.
Установите Origin CLI на агенте
Выполните шаги из раздела Предварительные требования; curl, git и jq должны быть в PATH.
Держите каталог рабочей копии пустым
Если агент сохраняет каталог сборки между сборками, очистите $BUILDKITE_BUILD_CHECKOUT_PATH до запуска хука. Команде clone-fast нужен пустой каталог, и при наличии в нём файлов она переключается на git clone.
Добавьте хук checkout
На self-hosted агенте сохраните приведённый ниже скрипт как checkout в каталоге --hooks-path агента. На агентах, размещённых в Buildkite, поставляйте его как хук checkout не-vendored плагина либо задайте checkout: { skip: true } на шаге и выполните те же команды в его command. Хук репозитория не может определять checkout, поскольку репозиторий ещё не выгружен. См. Agent hooks и Git checkout в документации Buildkite.
Хук запрашивает токен, ограниченный репозиторием пайплайна, экспортирует его как CURSOR_AUTH_TOKEN, клонирует репозиторий через clone-fast, регистрирует credential helper для git командой origin auth setup-git и выгружает $BUILDKITE_COMMIT. Токен агента передаётся в curl через stdin, поэтому он никогда не появляется в командной строке:
#!/usr/bin/env bashset -euo pipefailbody=$(printf '{"repo_url":"%s"}' "$BUILDKITE_REPO")token=$(printf 'Authorization: Token %s\n' "$BUILDKITE_AGENT_ACCESS_TOKEN" \ | curl -fsS -X POST -H @- \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data "$body" \ "${BUILDKITE_AGENT_ENDPOINT%/}/jobs/${BUILDKITE_JOB_ID}/cursor_origin_access_token" \ | jq -er '.token')# clone-fast принимает owner/repo, а не clone URL.repo=${BUILDKITE_REPO#https://origin.cursor.com/}repo=${repo#git/}repo=${repo%.git}export CURSOR_AUTH_TOKEN="$token"origin repo clone-fast "$repo" "$BUILDKITE_BUILD_CHECKOUT_PATH" --verifyorigin auth setup-gitcd "$BUILDKITE_BUILD_CHECKOUT_PATH"git fetch origin "$BUILDKITE_COMMIT"git checkout -q "$BUILDKITE_COMMIT"Когда build запускается без commit, BUILDKITE_COMMIT равен HEAD. Две строки git затем выполняют fetch remote HEAD и оставляют working tree на tip default branch, который clone-fast уже checked out.
- Передавайте
$BUILDKITE_REPOбез изменений.repo_urlдолжен в точности совпадать с URL-адресом репозитория, зарегистрированным в pipeline, включая.git. При другом написании возвращается HTTP 400. - Token является read-only и scoped на repository данного pipeline. Он содержит
repository:contents:readи истекает через 15 минут. Для последующего job или для git operation спустя более 15 минут после выпуска потребуется новый token из того же request. - Повторяйте попытку при 503. Если Agent API отвечает HTTP 503, дождитесь времени, указанного в header
Retry-After, и повторите попытку — так же, как это делает собственный credential helper Buildkite.
GitHub Actions и другие CI-поставщики
GitHub Actions и другие CI-системы выпускают собственные токены. Вы один раз создаёте Origin App, устанавливаете его в репозитории, которые клонируете, и передаёте каждому job приватный ключ приложения и два идентификатора. Job подписывает короткоживущий app JWT и обменивает его на installation token. Полное описание полей см. в разделах App JWT и Create Installation Access Token.
Создание Origin App
Для установки приложения необходимы права администратора рабочей области.
Регистрация приложения
Сгенерируйте пару ключей Ed25519
Принимаются только ключи Ed25519:
openssl genpkey -algorithm ED25519 -out origin-app-private.pemopenssl pkey -in origin-app-private.pem -pubout -out origin-app-public.pemСоздайте приложение и добавьте открытый ключ
В настройках приложений Origin создайте приложение и добавьте содержимое файла origin-app-public.pem в качестве ключа подписи. К приложению можно привязать до 10 активных ключей подписи.
Скопируйте идентификатор приложения
Идентификатор приложения указан на странице приложения. Идентификаторы приложения начинаются с app_.
Установка приложения
Установите приложение для своего владельца
На странице установки приложения в тех же настройках установите приложение для владельца, которому принадлежат клонируемые репозитории, и выберите эти репозитории.
Скопируйте installation id
Installation id указан в URL страницы установки: /codebase/settings/apps/installations/{installationId}. Installation id начинаются с i_.
Чтобы получать installation id программно, вызовите List App Installations, передав app JWT в качестве bearer-токена. В ответе для каждой установки возвращаются id, target.slug, scopes и repoSelectionMode.
Хранение credentials
Сохраните приватный ключ как секрет в вашей системе CI и передайте его в job через переменную окружения ORIGIN_APP_PRIVATE_KEY, содержащую полный текст PEM. Идентификатор приложения и installation id сохраните как обычные переменные ORIGIN_APP_ID и ORIGIN_INSTALLATION_ID. Если ваша система CI монтирует секреты как файлы, сначала загрузите ключ командой ORIGIN_APP_PRIVATE_KEY=$(cat /path/to/origin-app-private.pem).
Выпуск токена в job
Приведённая ниже функция подписывает app JWT и обменивает его на installation token. В JWT используется alg EdDSA, поля iss и kid задаются равными идентификатору приложения, aud — origin-apps, а exp — на 15 минут вперёд. В справочнике App JWT рекомендуется время жизни около пяти минут, но поскольку installation token не живёт дольше выпустившего его JWT, здесь подписывается JWT на 15 минут — чтобы у токена были все свои 15 минут. В запросе запрашивается repository:contents:read, чего достаточно для clone, fetch и pull. Для push нужен repository:contents:write, причём этот scope должен быть предоставлен при установке. Добавьте "repositoryIds":[...] в request body, чтобы ограничить токен частью репозиториев установки.
Приватный ключ передаётся в openssl через файловый дескриптор, а bearer-header — в curl через stdin, поэтому ни то, ни другое не появляется в командной строке, где их могли бы прочитать другие процессы на runner. Функция задаёт и экспортирует CURSOR_AUTH_TOKEN напрямую, так что токен нигде не сохраняется в файл, а неудачный выпуск останавливает job при включённом set -e. Потребуются bash, openssl 1.1.1 или новее, curl 7.55 или новее и jq:
origin_app_token() { b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } now=$(date +%s) header=$(printf '{"alg":"EdDSA","kid":"%s","typ":"JWT"}' "$ORIGIN_APP_ID" | b64url) claims=$(printf '{"iss":"%s","aud":"origin-apps","iat":%d,"exp":%d}' \ "$ORIGIN_APP_ID" "$now" "$((now + 900))" | b64url) # openssl -rawin требует входные данные с поддержкой позиционирования; подписываемые данные не содержат секретов. signing_input=$(mktemp) trap 'rm -f "$signing_input"' EXIT printf '%s.%s' "$header" "$claims" > "$signing_input" signature=$(openssl pkeyutl -sign -rawin -in "$signing_input" \ -inkey <(printf '%s\n' "$ORIGIN_APP_PRIVATE_KEY") | b64url) app_jwt="$header.$claims.$signature" CURSOR_AUTH_TOKEN=$(printf 'Authorization: Bearer %s\n' "$app_jwt" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' \ --data '{"scopes":["repository:contents:read"]}' \ "https://api.cursor.com/v1/origin/app/installations/${ORIGIN_INSTALLATION_ID}/access_tokens" \ | jq -er '.token') export CURSOR_AUTH_TOKEN}Вызовите её непосредственно перед клонированием в пустой каталог, а затем зарегистрируйте git credential helper, чтобы последующие команды git проходили аутентификацию, пока CURSOR_AUTH_TOKEN остаётся экспортированным. Замените acme/widgets на свой репозиторий, а COMMIT_SHA — на коммит, который ваша CI-система предоставляет для сборки:
set -euo pipefailorigin_app_tokenorigin repo clone-fast acme/widgets . --verifyorigin auth setup-gitgit fetch origin "$COMMIT_SHA"git checkout -q "$COMMIT_SHA"GitHub Actions
GitHub Actions использует вариант с Origin App: приватный ключ хранится в секрете Actions, а два идентификатора — в переменных репозитория. Поскольку workflow запускает GitHub, репозиторий находится на GitHub, а Origin хранит его как зеркало. Workflow просит Origin подтянуть коммит сборки, клонирует репозиторий командой clone-fast в пустой $GITHUB_WORKSPACE, а затем переключается на этот коммит. Он вообще не обращается к GitHub, поэтому ему не нужны никакие права доступа GITHUB_TOKEN и не требуется actions/checkout.
Добавьте секрет
В своём репозитории выберите Settings > Secrets and variables > Actions, затем добавьте секрет ORIGIN_APP_PRIVATE_KEY с полным PEM приватного ключа.
Добавьте переменные
На той же странице добавьте переменные репозитория ORIGIN_APP_ID и ORIGIN_INSTALLATION_ID.
Добавьте workflow
Сохраните приведённый ниже workflow как .github/workflows/ci.yml, укажите в ORIGIN_REPO значение {owner}/{repo} зеркала на Origin и добавьте свои шаги сборки после шага клонирования.
Workflow устанавливает CLI, выпускает токен, ждёт, пока Origin отзеркалит коммит, и клонирует его:
name: cion: push: branches: [main] pull_request:permissions: {}jobs: build: runs-on: ubuntu-latest defaults: run: shell: bash steps: - name: Install the Origin CLI run: | curl -fsSL https://downloads.cursor.com/origin/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Clone from Origin with clone-fast env: ORIGIN_APP_ID: ${{ vars.ORIGIN_APP_ID }} ORIGIN_INSTALLATION_ID: ${{ vars.ORIGIN_INSTALLATION_ID }} ORIGIN_APP_PRIVATE_KEY: ${{ secrets.ORIGIN_APP_PRIVATE_KEY }} ORIGIN_REPO: acme/widgets BUILD_BRANCH: ${{ github.head_ref || github.ref_name }} BUILD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | set -euo pipefail origin_app_token() { b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } now=$(date +%s) header=$(printf '{"alg":"EdDSA","kid":"%s","typ":"JWT"}' "$ORIGIN_APP_ID" | b64url) claims=$(printf '{"iss":"%s","aud":"origin-apps","iat":%d,"exp":%d}' \ "$ORIGIN_APP_ID" "$now" "$((now + 900))" | b64url) signing_input=$(mktemp) trap 'rm -f "$signing_input"' EXIT printf '%s.%s' "$header" "$claims" > "$signing_input" signature=$(openssl pkeyutl -sign -rawin -in "$signing_input" \ -inkey <(printf '%s\n' "$ORIGIN_APP_PRIVATE_KEY") | b64url) app_jwt="$header.$claims.$signature" echo "::add-mask::$app_jwt" CURSOR_AUTH_TOKEN=$(printf 'Authorization: Bearer %s\n' "$app_jwt" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' \ --data '{"scopes":["repository:contents:read"]}' \ "https://api.cursor.com/v1/origin/app/installations/${ORIGIN_INSTALLATION_ID}/access_tokens" \ | jq -er '.token') echo "::add-mask::$CURSOR_AUTH_TOKEN" export CURSOR_AUTH_TOKEN } origin_app_token # Origin зеркалирует GitHub с задержкой. Ждём этот коммит примерно до двух минут. body=$(printf '{"ref":"refs/heads/%s","sha":"%s","wait":true}' "$BUILD_BRANCH" "$BUILD_SHA") printf 'Authorization: Bearer %s\n' "$CURSOR_AUTH_TOKEN" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' --data "$body" \ "https://api.cursor.com/v1/origin/repos/${ORIGIN_REPO}:syncMirror" \ | jq -e '.synced' > /dev/null \ || { echo "Origin has not mirrored $BUILD_SHA from $BUILD_BRANCH yet" >&2; exit 1; } origin repo clone-fast "$ORIGIN_REPO" . --verify origin auth setup-git git fetch origin "$BUILD_SHA" git checkout -q "$BUILD_SHA"- При
pull_requestworkflow собирает head-ветку pull request.github.sha— это merge-коммит, который существует только на GitHub, поэтомуBUILD_SHAиBUILD_BRANCHиспользуют вместо него head-коммит и ветку pull request. - Запрос синхронизации — это Sync Mirror. Он принимает installation token, требует
repository:contents:readи возвращает200с"synced": trueлибо202с"synced": falseпо истечении лимита ожидания около двух минут. Если source of truth — Origin, а GitHub является зеркалом, удалите запрос синхронизации: коммит уже есть в Origin, а Origin отклоняет запросы синхронизации для репозиториев, которые не подтягивают изменения из upstream-источника. - Оба credentials маскируются.
::add-mask::скрывает app JWT и installation token в логе задачи. - Более поздние шаги выпускают токен заново. Более поздний шаг, которому нужен git-доступ к Origin, снова определяет и вызывает функцию. Токен никогда не записывается в
$GITHUB_ENVили$GITHUB_OUTPUT— это файлы на диске. - Pull requests из форков не зеркалируются. Их head-ветки нет в вашем репозитории. Собирайте такие изменения из GitHub.
Другие CI-системы
GitLab CI, Jenkins, CircleCI и self-hosted fleet используют способ с Origin App напрямую. Сохраните приватный ключ и два идентификатора, как описано в разделе Store the credentials, затем вызовите функцию из раздела Mint a token in the job непосредственно перед origin repo clone-fast, а затем выполните fetch и check out того commit, который ваша CI-система предоставляет для сборки.
Проверка первого запуска
Запустите первый job с флагом --fallback never. В этом случае отсутствующий kit завершит job с кодом выхода 1, а не приведёт к незаметному запуску медленного git clone. Не убирайте этот флаг, пока путь через kit не начнёт срабатывать успешно.
При успешном запуске в stderr выводятся clone-kit: manifest=..., по одной строке загрузки на каждый artifact, блок clone-kit timings: и clone-kit: ready DIR (head SHA), затем в stdout — mode: clone-kit, и запуск завершается с кодом 0. Блок timings разбивает запуск на фазы manifest, download, verify и checkout. Чтобы оценить выигрыш, сравните его итоговое время с обычным git clone того же repository на том же runner.
Если путь через kit не удаётся завершить, в stderr появляется одна машиночитаемая строка — clone-kit-result: status=fallback phase=PHASE или clone-kit-result: status=failed phase=PHASE — а за ней указывается причина. При значении по умолчанию --fallback auto в stdout затем выводится mode: git-clone.
Устранение неполадок
Каждая запись начинается со строки, которая видна в журнале задания.
Загрузка манифеста возвращает 404
clone-kit-result: status=fallback phase=manifest с HTTP 404. Для репозитория ещё нет набора для клонирования; обратитесь к команде вашего аккаунта Cursor, чтобы включить CloneKit. Если в URL из сообщения https:// встречается дважды, значит команда получила URL для клонирования вместо {owner}/{repo}.
Загрузка манифеста возвращает 401
clone-kit manifest fetch failed: HTTP 401. Origin отклонил токен: он истёк, никогда не был действительным либо его установка была удалена. Выпустите новый токен непосредственно перед клонированием. При значении по умолчанию --fallback auto резервный git clone завершается с тем же кодом и кодом выхода 128, поэтому в журнале видны обе ошибки.
Получение манифеста возвращает 403
clone-kit manifest fetch failed: HTTP 403. Установка или токен конвейера Buildkite не распространяется на этот репозиторий. Переустановите приложение или заново выберите охватываемые репозитории. Как и при 401, резервный git clone завершается с тем же кодом и кодом выхода 128.
Git запрашивает имя пользователя
fatal: could not read Username for 'https://origin.cursor.com'. Либо версия CLI устарела, либо переменная CURSOR_AUTH_TOKEN не экспортирована в shell, который запускает git. Выполните origin update или экспортируйте переменную.
Клонирование переходит в резервный режим на этапе аутентификации
clone-kit-result: status=fallback phase=auth с Not authenticated. Переменная CURSOR_AUTH_TOKEN не экспортирована в процесс, который запускает origin. Экспортируйте её в том же shell перед командой origin.
Выпуск токена возвращает 401 Issuer is not authorized
401 {"code":16,"message":"Issuer is not authorized"} от token endpoint. Claim iss не является зарегистрированным App ID, либо ключ подписи не зарегистрирован для этого приложения. Убедитесь, что ORIGIN_APP_ID соответствует приложению и что этот ключ подписи указан в списке ключей приложения.
origin auth status сообщает о действительном токене, но клонирование не удаётся
origin auth status определяет Token: valid или Token: expired по claim со сроком действия внутри CURSOR_AUTH_TOKEN. Команда не спрашивает Origin, принимает ли он токен, поэтому valid означает лишь то, что срок действия токена не истёк. CLI считает токен истёкшим за пять минут до времени, указанного в claim со сроком действия. Команды origin отклоняют истёкший токен ещё до отправки запроса, а clone-fast записывает в журнал clone-kit-result: status=fallback phase=auth с сообщением The injected CURSOR_AUTH_TOKEN session has expired. Выпустите новый токен и повторите попытку.
Если путь через набор по-прежнему не работает, отправьте команде вашего аккаунта Cursor журнал задания со строкой clone-kit-result, название репозитория и выходные данные команды origin --version.