origin repo clone-fast を使って CI でクローンキットをセットアップする
Origin は現在 early beta として提供されています。リポジトリの作成、git でのプッシュとプル、GitHub からの mirror、コードのブラウズと検索、プルリクエストのオープンとマージ、Cherri Code チームとの共有が可能です。
製品改善のため、ご意見・ご要望はぜひ [email protected] までお寄せください。
origin repo clone-fast は、CI ジョブでの git clone を置き換えるコマンドです。ゼロからクローンするのではなく、あらかじめビルドされたクローンキット (クローンキット を有効にしたリポジトリごとに Cherri Code がビルドする、パックされたリポジトリのスナップショット) をダウンロードし、キットのビルド後に追加されたオブジェクトのみを fetch します。このページでは、短命な Origin トークンをランナーに渡す方法と、Buildkite、GitHub Actions、その他の CI システム でこのコマンドを実行する方法を説明します。
仕組み
origin repo clone-fast {owner}/{repo} [DIR] を実行するたびに、以下の処理が行われます。
CURSOR_AUTH_TOKENのトークンを使って、Origin からキットのマニフェストを取得します。マニフェストには、キットのアーティファクトと tip のコミットが記載されています。gitcdn.origin.cursor.comからパックのアーティファクトをダウンロードし、そのサイズを確認します。--verifyを指定すると、ダウンロード中にハッシュも確認します。- 空である必要がある宛先ディレクトリにインストールします。
- クローンを top-up します。キットのビルド以降に追加されたオブジェクトを取得し、default branch の現在の tip をチェックアウトします。
--no-top-upを指定した場合、チェックアウト先はキットの tip のままです。--bareは、ワーキングツリーを持たない bare repository を作成します。
キット経路が成功すると、コマンドは標準出力に mode: clone-kit を出力します。いずれかのステップが失敗した場合は、デフォルトの --fallback auto により通常の git clone が実行され、mode: git-clone が出力されます。代わりにステータス 1 で終了させたい場合は --fallback never を指定してください。
トークンとチェックアウトの挙動は、どの CI システムでも同じです。
- トークンは最大 15 分で期限切れとなり、更新はできません。 クローンの直前に mint してください。
- CLI は
CURSOR_AUTH_TOKENからトークンを読み取ります。originを実行する shell でエクスポートしてください。 - クローン完了時点では default branch の tip にいます。 特定のコミットをビルドするには、その後に
git fetch origin SHAとgit checkout SHAを実行してください。Origin は到達可能なコミットであれば SHA で提供します。
Prerequisites
クローンキットはエンタープライズプランで利用でき、リポジトリ単位で有効化されます。セルフサーブでの切り替えはできません。クローンする各リポジトリで有効にするには、Cherri Code アカウントチームにお問い合わせください。コマンドのオプション一覧は origin repo clone-fast --help で確認できます。
- ランナー上の Origin CLI。
curl -fsSL https://downloads.cursor.com/origin/install.sh | shでインストールすると、バイナリは$HOME/.local/bin/originに配置されます。Linux ランナーでは x64 または arm64 上の glibc が必要です。Alpine やその他の musl イメージは非対応です。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 | キットのマニフェストと git 操作 |
gitcdn.origin.cursor.com | キットのダウンロード |
api.cursor.com | トークンの発行と Origin App からのミラー同期リクエスト |
パスを選ぶ
| CI システム | トークンの取得元 | セクション |
|---|---|---|
| Buildkite (ホスト型またはセルフホストのエージェント、Origin をリポジトリ プロバイダーとして接続済み) | Buildkite Agent API (checkout フック内) | Buildkite |
| GitHub Actions | Origin App (ワークフローのステップ内) | GitHub Actions |
| GitLab CI、Jenkins、CircleCI、セルフホストのワーカー群 | Origin App (ジョブ内) | その他の CI システム |
Buildkite
トークンは Buildkite が発行します。エージェントのデフォルトの Git チェックアウトを、Buildkite Agent API にトークンを要求して origin repo clone-fast を実行する checkout フックに置き換えてください。
Origin を接続するには Buildkite の organization 管理者、Buildkite アプリをインストールするには Origin の管理者である必要があります。
Origin を Buildkite に接続する
Buildkite で Settings > Repository Providers > Add Provider > Origin の順に選択するか、New Pipeline ページで Connect Origin account を選択します。オーナーとリポジトリを選択し、Buildkite アプリをインストールします。Buildkite は、リポジトリのコンテンツと Pull Requests への読み取り権限、およびチェックへの読み取り・書き込み権限を要求します。詳細は Buildkite のドキュメントの Origin を参照してください。
エージェントに Origin CLI をインストールする
前提条件に従い、curl、git、jq を PATH に配置します。
チェックアウトディレクトリを空にしておく
エージェントがビルド間でビルドディレクトリを保持する場合は、フックの実行前に $BUILDKITE_BUILD_CHECKOUT_PATH を空にしてください。clone-fast は空のディレクトリを必要とし、ファイルが残っていると git clone にフォールバックします。
checkout フックを追加する
セルフホストのエージェントでは、以下のスクリプトをエージェントの --hooks-path ディレクトリに checkout という名前で保存します。Buildkite ホステッドエージェントでは、vendored でないプラグインの checkout フックとして配布するか、ステップに checkout: { skip: true } を設定し、ステップの command で同じコマンドを実行します。リポジトリはまだチェックアウトされていないため、リポジトリフックで checkout を定義することはできません。Buildkite のドキュメントの Agent hooks と Git checkout を参照してください。
このフックは、パイプラインのリポジトリにスコープされたトークンを要求し、CURSOR_AUTH_TOKEN としてエクスポートしたうえで clone-fast でクローンし、origin auth setup-git で Git の認証情報ヘルパーを登録して、$BUILDKITE_COMMIT をチェックアウトします。エージェントトークンは stdin 経由で curl に渡されるため、コマンドラインに現れることはありません。
#!/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 に渡すのは clone URL ではなく owner/repo です。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"コミットを指定せずにビルドが開始された場合、BUILDKITE_COMMIT は HEAD になります。続く 2 行の git コマンドがリモートの HEAD を取得し、clone-fast がすでにチェックアウトしたデフォルトブランチの tip にワーキングツリーを残します。
$BUILDKITE_REPOはそのまま送信してください。repo_urlは、パイプラインに登録されたリポジトリ URL と.gitを含めて完全に一致していなければなりません。表記が異なると HTTP 400 が返ります。- トークンは参照専用で、パイプラインのリポジトリにスコープされています。
repository:contents:readを持ち、15 分で失効します。後続のジョブや、mint から 15 分以上経過した git 操作では、同じリクエストで新しいトークンを取得する必要があります。 - 503 の場合はリトライしてください。 Agent API が HTTP 503 を返した場合は、その
Retry-Afterヘッダーに従って待機してからリトライします。Buildkite 自身の認証情報ヘルパーと同じ動作です。
GitHub Actions とその他の CI providers
GitHub Actions などの CI システムは、それぞれ独自の token を 発行 します。Origin App を一度作成し、clone 対象のリポジトリにインストールしたうえで、各 ジョブ にアプリの 秘密鍵 と 2 つの ID を渡します。ジョブ は 短期有効な app JWT に署名し、それを installation token と交換します。field の詳細なリファレンスは、App JWT と Create Installation Access Token を参照してください。
Origin App を作成する
アプリのインストールには Workspace 管理者権限が必要です。
アプリを登録する
Ed25519 キーペアを生成する
使用できるのは Ed25519 キーのみです。
openssl genpkey -algorithm ED25519 -out origin-app-private.pemopenssl pkey -in origin-app-private.pem -pubout -out origin-app-public.pemアプリを作成して公開鍵を追加する
Origin app settings でアプリを作成し、origin-app-public.pem のコンテンツを署名キーとして追加します。1 つのアプリで保持できる有効な署名キーは最大 10 個です。
App ID をコピーする
App ID はアプリのページに表示されます。App ID は app_ で始まります。
アプリのインストール
owner にアプリをインストールする
同じ設定内にあるアプリのインストールページから、クローン対象のリポジトリを保有する owner にアプリをインストールし、対象のリポジトリを選択します。
installation id をコピーする
installation id は、インストールページの URL /codebase/settings/apps/installations/{installationId} に含まれています。installation id は i_ で始まります。
installation id をプログラム経由で読み取るには、app JWT を bearer として List App Installations を呼び出します。レスポンスには各インストールの id、target.slug、scopes、repoSelectionMode が含まれます。
認証情報を保存する
秘密鍵はCIシステムのシークレットとして保存し、PEMテキスト全体を含む環境変数ORIGIN_APP_PRIVATE_KEYとしてジョブに渡します。App IDとインストールIDは、通常の変数ORIGIN_APP_IDおよびORIGIN_INSTALLATION_IDとして保存します。CIシステムがシークレットをファイルとしてマウントする場合は、まずORIGIN_APP_PRIVATE_KEY=$(cat /path/to/origin-app-private.pem)で鍵を読み込んでください。
ジョブ 内でトークンを 発行 する
以下の関数は app JWT に署名し、それを installation token と交換します。この JWT は alg に EdDSA を使用し、iss と kid に App ID、aud に origin-apps を設定し、exp は 15 分先に設定します。App JWT のリファレンスでは有効期間を約 5 分とすることを推奨していますが、installation token は それを 発行 した JWT より長く存続することはないため、このレシピでは 15 分の JWT に署名し、トークンが 15 分をフルに使えるようにしています。リクエストで要求している repository:contents:read は、clone、フェッチ、プル をカバーします。push には repository:contents:write が必要で、その スコープ が installation で grant されている必要があります。トークンの適用範囲を installation の一部の リポジトリ に絞り込むには、request body に "repositoryIds":[...] を追加してください。
秘密鍵 はファイルディスクリプタ経由で openssl に、bearer header は stdin 経由で curl に渡されるため、どちらもコマンドラインには現れません。コマンドラインに現れると、ランナー 上の他の processes から読み取られる可能性があります。この関数は CURSOR_AUTH_TOKEN を直接 set してエクスポートするため、トークンがファイルに書き出されることはなく、発行 に失敗した場合は set -e により ジョブ が stop します。実行には 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 にはシーク可能な入力が必要。署名入力に secret は含まれない。 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 の認証情報ヘルパーを登録します。こうすることで、CURSOR_AUTH_TOKEN がエクスポートされたままの状態で、以降の git コマンドが認証されます。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 のシークレットに、2 つの ID をリポジトリ変数に格納します。ワークフローを起動するのは GitHub なので、リポジトリは GitHub 上にあり、Origin はそれをミラーとして保持します。ワークフローは Origin にビルド対象のコミットの取得を依頼し、空の $GITHUB_WORKSPACE に clone-fast でクローンしたうえで、そのコミットをチェックアウトします。GitHub を一切呼び出さないため、GITHUB_TOKEN の権限は不要で、actions/checkout も使用しません。
シークレットを追加する
リポジトリで Settings > Secrets and variables > Actions を選び、秘密鍵 の PEM 全文を ORIGIN_APP_PRIVATE_KEY というシークレットとして追加します。
変数を追加する
同じページで、リポジトリ変数 ORIGIN_APP_ID と ORIGIN_INSTALLATION_ID を追加します。
ワークフローを追加する
以下のワークフローを .github/workflows/ci.yml として保存し、ORIGIN_REPO に Origin 上のミラーの {owner}/{repo} を設定して、クローンのステップの後にビルドのステップを追加します。
このワークフローは、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 のミラーリングには遅延があります。この commit を最大 2 分程度待機します。 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_requestでは、workflow はプルリクエストの head を build します。github.shaは GitHub 上にのみ存在する merge commit のため、BUILD_SHAとBUILD_BRANCHには代わりにプルリクエストの head commit とブランチを使用します。- sync リクエストは Sync Mirror です。 installation token を受け入れ、
repository:contents:readが必要で、"synced": trueとともに200を返すか、約 2 分の待機 budget の後に"synced": falseとともに202を返します。Origin が信頼できる情報源で GitHub がミラーの場合は、sync リクエストを削除してください。commit はすでに Origin 上にあり、また Origin は上流ソースから pull しないリポジトリに対する sync リクエストを拒否します。 - 両方の credentials はマスクされます。
::add-mask::は ジョブ ログ内で app JWT と installation token を隠します。 - 後続の step では改めて mint します。 Origin の git アクセスが必要な後続の step では、関数を再度定義して呼び出します。token は
$GITHUB_ENVや$GITHUB_OUTPUTに一切書き出されません。これらはディスク上のファイルです。 - フォークからのプルリクエストはミラーリングされません。 その head branch は自分のリポジトリには存在しません。それらは GitHub から build してください。
その他のCIシステム
GitLab CI、Jenkins、CircleCI、セルフホストのワーカー群では、Origin App のパスを直接使用します。credentialsを保存する の説明に従って private key と2つのidを保存し、origin repo clone-fast の直前に ジョブでtokenをmintする の関数を実行したうえで、CIシステムがビルド向けに公開しているcommitをフェッチしてcheck outしてください。
初回実行の確認
最初の ジョブ は --fallback never を付けて実行します。キットが見つからない場合、遅い git clone が黙って実行されるのではなく、終了ステータス 1 で ジョブ が失敗します。キットのパスが成功するまで、このフラグは付けたままにしてください。
実行に成功すると、stderr に clone-kit: manifest=...、アーティファクトごとに 1 行のダウンロード行、clone-kit timings: ブロック、clone-kit: ready DIR (head SHA) が出力され、続いて stdout に mode: clone-kit が出力されて、終了コード 0 で終了します。timings ブロックでは、実行を マニフェスト、download、verify、checkout の各フェーズに分けて表示します。効果を測定するには、その合計時間を、同じ ランナー 上で同じリポジトリを通常の git clone した場合と比較してください。
キットのパスが完了しなかった場合、stderr には機械可読な 1 行 clone-kit-result: status=fallback phase=PHASE または clone-kit-result: status=failed phase=PHASE が出力され、続いて理由が出力されます。デフォルトの --fallback auto では、その後 stdout に mode: git-clone が表示されます。
トラブルシューティング
各項目は、ジョブ のログに出力される行から始まります。
マニフェストの fetch が 404 を返す
clone-kit-result: status=fallback phase=manifest と HTTP 404。そのリポジトリのクローンキットがまだ存在しません。Cherri Code アカウント チームに クローンキット の有効化を依頼してください。メッセージ内の URL に https:// が 2 回含まれている場合は、コマンドが {owner}/{repo} ではなく clone URL を受け取っています。
マニフェストの fetch が 401 を返す
clone-kit manifest fetch failed: HTTP 401。Origin がトークンを拒否しました。期限切れである、そもそも有効ではなかった、またはそのインストールが削除されています。クローンの直前に新しいトークンをmintしてください。デフォルトの --fallback auto では、フォールバックの git clone も同じコードと exit ステータス 128 で失敗するため、ログには両方のエラーが出力されます。
マニフェストの fetch が 403 を返す
clone-kit manifest fetch failed: HTTP 403。インストール、または Buildkite のパイプライントークンがこのリポジトリを対象に含んでいません。app を再インストールするか、対象リポジトリを選択し直してください。401 と同様に、フォールバックの git clone も同じコードと exit ステータス 128 で失敗します。
Git がユーザー名を要求する
fatal: could not read Username for 'https://origin.cursor.com'。CLI が古い、または git を実行する shell で CURSOR_AUTH_TOKEN がエクスポートされていません。origin update を実行するか、変数をエクスポートしてください。
auth フェーズでクローンがフォールバックする
clone-kit-result: status=fallback phase=auth と Not authenticated。origin を実行する process に CURSOR_AUTH_TOKEN がエクスポートされていません。origin コマンドの前に、同じ shell でエクスポートしてください。
mint が 401 Issuer is not authorized を返す
トークン endpoint から 401 {"code":16,"message":"Issuer is not authorized"} が返される場合、iss claim が登録済みの App ID でないか、その app に署名キーが登録されていません。ORIGIN_APP_ID が app と一致しているか、また app に署名キーが登録されているかを確認してください。
origin auth status はトークンが有効と報告するがクローンが失敗する
origin auth status は、CURSOR_AUTH_TOKEN 内の有効期限のclaimから Token: valid または Token: expired を判定します。Origin にトークンを受け入れるかどうかを問い合わせているわけではないため、valid はトークンが失効していないことを示すにすぎません。CLI は、有効期限のclaimの 5 分前からトークンを失効扱いにします。origin コマンドは、失効したトークンの場合はリクエストを送信する前に処理を拒否し、clone-fast は The injected CURSOR_AUTH_TOKEN session has expired とともに clone-kit-result: status=fallback phase=auth をログに記録します。新しいトークンをmintして再試行してください。
キットのパスでも失敗する場合は、clone-kit-result の行を含む ジョブ のログ、リポジトリ、origin --version の出力を Cherri Code アカウントチームに送ってください。