diff --git a/.changeset/recap-claude-oauth-token.md b/.changeset/recap-claude-oauth-token.md new file mode 100644 index 0000000000..493d9ecf72 --- /dev/null +++ b/.changeset/recap-claude-oauth-token.md @@ -0,0 +1,6 @@ +--- +"@agent-native/recap-cli": minor +"@agent-native/core": patch +--- + +Accept `CLAUDE_CODE_OAUTH_TOKEN` as an alternative to `ANTHROPIC_API_KEY` for the PR visual recap claude backend, so recaps can bill a Claude subscription instead of API credits. `recapRequiredSecrets` now returns interchangeable secret names, and `recap setup` / `recap doctor` accept either credential. diff --git a/.github/workflows/pr-visual-recap-fork.yml b/.github/workflows/pr-visual-recap-fork.yml index a24248bc8d..aa139fbdb5 100644 --- a/.github/workflows/pr-visual-recap-fork.yml +++ b/.github/workflows/pr-visual-recap-fork.yml @@ -65,6 +65,7 @@ jobs: # Presence-only signals — never expose secret VALUES to the gate. HAS_PLAN: ${{ secrets.PLAN_RECAP_TOKEN != '' }} HAS_ANTHROPIC: ${{ secrets.ANTHROPIC_API_KEY != '' }} + HAS_CLAUDE_OAUTH: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }} HAS_OPENAI: ${{ secrets.OPENAI_API_KEY != '' }} HAS_COMPATIBLE: ${{ secrets.VISUAL_RECAP_API_KEY != '' }} AGENT: ${{ env.VISUAL_RECAP_AGENT }} @@ -196,7 +197,7 @@ jobs: } else if (agent === 'codex') { if (process.env.HAS_OPENAI !== 'true') reasons.push('OPENAI_API_KEY not configured (codex backend)'); } else if (agent === 'claude') { - if (process.env.HAS_ANTHROPIC !== 'true') reasons.push('ANTHROPIC_API_KEY not configured (claude backend)'); + if (process.env.HAS_ANTHROPIC !== 'true' && process.env.HAS_CLAUDE_OAUTH !== 'true') reasons.push('neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)'); } else { if (process.env.HAS_COMPATIBLE !== 'true') reasons.push('VISUAL_RECAP_API_KEY not configured (openai-compatible backend)'); if (!(process.env.VISUAL_RECAP_MODEL || '').trim()) reasons.push('VISUAL_RECAP_MODEL is required (openai-compatible backend)'); @@ -579,8 +580,14 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + # A secret the repo never configured still arrives here as an empty + # env var. Drop those so the CLI picks the credential that is actually + # set instead of treating an empty one as the chosen auth path. + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write,Bash(git diff:*)" CLAUDE_ARGS=(-p "$(cat recap-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") @@ -746,8 +753,11 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write" CLAUDE_ARGS=(-p "$(cat recap-repair-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") diff --git a/.github/workflows/pr-visual-recap-reusable.yml b/.github/workflows/pr-visual-recap-reusable.yml index 3f72908f04..b3328027d1 100644 --- a/.github/workflows/pr-visual-recap-reusable.yml +++ b/.github/workflows/pr-visual-recap-reusable.yml @@ -12,6 +12,8 @@ name: PR Visual Recap (reusable) # secrets: # PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} +# # …or bill the claude backend to a Claude subscription instead: +# # CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # # IMPORTANT: callers must trigger on the same pull_request event types as this # file declares in its on.workflow_call section (opened, synchronize, reopened, @@ -110,6 +112,12 @@ on: # Required for the default claude backend; optional when agent=codex. ANTHROPIC_API_KEY: required: false + # Alternative to ANTHROPIC_API_KEY for the claude backend: a Claude Code + # subscription OAuth token minted with `claude setup-token`, which bills + # the run to the subscription instead of API credits. Set exactly one — + # with both configured the billing path is whichever the CLI prefers. + CLAUDE_CODE_OAUTH_TOKEN: + required: false # Required when agent=codex; ignored otherwise. OPENAI_API_KEY: required: false @@ -152,6 +160,7 @@ jobs: # Presence-only signals — never expose secret VALUES to the gate. HAS_PLAN: ${{ secrets.PLAN_RECAP_TOKEN != '' }} HAS_ANTHROPIC: ${{ secrets.ANTHROPIC_API_KEY != '' }} + HAS_CLAUDE_OAUTH: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }} HAS_OPENAI: ${{ secrets.OPENAI_API_KEY != '' }} HAS_COMPATIBLE: ${{ secrets.VISUAL_RECAP_API_KEY != '' }} AGENT: ${{ inputs.agent }} @@ -230,7 +239,7 @@ jobs: } else if (agent === 'codex') { if (process.env.HAS_OPENAI !== 'true') reasons.push('OPENAI_API_KEY not configured (codex backend)'); } else if (agent === 'claude') { - if (process.env.HAS_ANTHROPIC !== 'true') reasons.push('ANTHROPIC_API_KEY not configured (claude backend)'); + if (process.env.HAS_ANTHROPIC !== 'true' && process.env.HAS_CLAUDE_OAUTH !== 'true') reasons.push('neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)'); } else { if (process.env.HAS_COMPATIBLE !== 'true') reasons.push('VISUAL_RECAP_API_KEY not configured (openai-compatible backend)'); if (!(process.env.VISUAL_RECAP_MODEL || '').trim()) reasons.push('VISUAL_RECAP_MODEL is required (openai-compatible backend)'); @@ -588,8 +597,14 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + # A secret the caller never configured still arrives here as an empty + # env var. Drop those so the CLI picks the credential that is actually + # set instead of treating an empty one as the chosen auth path. + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write,Bash(git diff:*)" CLAUDE_ARGS=(-p "$(cat recap-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") @@ -755,8 +770,11 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write" CLAUDE_ARGS=(-p "$(cat recap-repair-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") diff --git a/.github/workflows/pr-visual-recap.yml b/.github/workflows/pr-visual-recap.yml index 9466885421..303eeccf18 100644 --- a/.github/workflows/pr-visual-recap.yml +++ b/.github/workflows/pr-visual-recap.yml @@ -46,6 +46,7 @@ jobs: # Presence-only signals — never expose secret VALUES to the gate. HAS_PLAN: ${{ secrets.PLAN_RECAP_TOKEN != '' }} HAS_ANTHROPIC: ${{ secrets.ANTHROPIC_API_KEY != '' }} + HAS_CLAUDE_OAUTH: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }} HAS_OPENAI: ${{ secrets.OPENAI_API_KEY != '' }} HAS_COMPATIBLE: ${{ secrets.VISUAL_RECAP_API_KEY != '' }} AGENT: ${{ env.VISUAL_RECAP_AGENT }} @@ -125,7 +126,7 @@ jobs: } else if (agent === 'codex') { if (process.env.HAS_OPENAI !== 'true') reasons.push('OPENAI_API_KEY not configured (codex backend)'); } else if (agent === 'claude') { - if (process.env.HAS_ANTHROPIC !== 'true') reasons.push('ANTHROPIC_API_KEY not configured (claude backend)'); + if (process.env.HAS_ANTHROPIC !== 'true' && process.env.HAS_CLAUDE_OAUTH !== 'true') reasons.push('neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)'); } else { if (process.env.HAS_COMPATIBLE !== 'true') reasons.push('VISUAL_RECAP_API_KEY not configured (openai-compatible backend)'); if (!(process.env.VISUAL_RECAP_MODEL || '').trim()) reasons.push('VISUAL_RECAP_MODEL is required (openai-compatible backend)'); @@ -522,8 +523,14 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + # A secret the repo never configured still arrives here as an empty + # env var. Drop those so the CLI picks the credential that is actually + # set instead of treating an empty one as the chosen auth path. + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write,Bash(git diff:*)" CLAUDE_ARGS=(-p "$(cat recap-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") @@ -694,8 +701,11 @@ jobs: continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | set -uo pipefail + [ -n "${ANTHROPIC_API_KEY:-}" ] || unset ANTHROPIC_API_KEY + [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ALLOWED_TOOLS="Read,Write" CLAUDE_ARGS=(-p "$(cat recap-repair-prompt.md)" --allowedTools "$CLAUDE_ALLOWED_TOOLS" --permission-mode dontAsk --output-format json) CLAUDE_ARGS+=(--model "${VISUAL_RECAP_MODEL:-claude-sonnet-5}") diff --git a/packages/core/docs/content/locales/ar-SA/pr-visual-recap.mdx b/packages/core/docs/content/locales/ar-SA/pr-visual-recap.mdx index f0e504407a..a6d171438a 100644 --- a/packages/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/ar-SA/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor إذا لم يتم تعيين المتغير، يستخدم الإجراء `claude`. +تصادق واجهة `claude` الخلفية باستخدام أيٍّ من بيانات الاعتماد: يحتسب `ANTHROPIC_API_KEY` تكلفة الرموز على حساب Anthropic API الخاص بك، بينما يحتسب `CLAUDE_CODE_OAUTH_TOKEN` — الذي يُنشأ محليًا بالأمر `claude setup-token` على اشتراك Claude — تكلفة التشغيل على ذلك الاشتراك. عيّن واحدًا فقط؛ فإذا ضُبط كلاهما، فسيعتمد مسار الفوترة على ما تفضّله واجهة Claude Code CLI. + ## النموذج والمنطق خارج الواجهة الخلفية، يوجد متغيران في المستودع يضبطان _كيف_ يعمل الوكيل: @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### الأسرار (مطلوب اثنان فقط) -| سرية | الغرض | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | رمز مميز قابل للإلغاء تم سكه بواسطة `npx @agent-native/core@latest connect`. يسمح بنشر خطة الملخّص وتحميل لقطة الشاشة. | -| `ANTHROPIC_API_KEY` | مفتاح LLM للواجهة الخلفية الافتراضية لرمز Claude. | +| سرية | الغرض | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | رمز مميز قابل للإلغاء تم سكه بواسطة `npx @agent-native/core@latest connect`. يسمح بنشر خطة الملخّص وتحميل لقطة الشاشة. | +| `ANTHROPIC_API_KEY` | مفتاح LLM للواجهة الخلفية الافتراضية لرمز Claude. | +| `CLAUDE_CODE_OAUTH_TOKEN` | بديل عن `ANTHROPIC_API_KEY`: رمز اشتراك Claude يُنشأ بالأمر `claude setup-token`. عيّن أحدهما فقط وليس كليهما. | **الفرق: استخدم رمزًا مميزًا لخدمة المؤسسة.** الرمز المميز الشخصي مرتبط بالشخص من قام بسكها - إذا تركوا المؤسسة أو أبطلوا رموزهم المميزة، فسيتم استخدام كل عملية إعادة شراء @@ -494,6 +497,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/de-DE/pr-visual-recap.mdx b/packages/core/docs/content/locales/de-DE/pr-visual-recap.mdx index 36830d79c0..1dc93c4ae1 100644 --- a/packages/core/docs/content/locales/de-DE/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/de-DE/pr-visual-recap.mdx @@ -156,6 +156,8 @@ Wählen Sie mit der Repository-Variable `VISUAL_RECAP_AGENT` aus, welcher Codier Wenn die Variable nicht gesetzt ist, verwendet die Aktion `claude`. +Das `claude`-Backend authentifiziert sich mit einem der beiden Anmeldedaten: `ANTHROPIC_API_KEY` rechnet Tokens über Ihr Anthropic-API-Konto ab, während `CLAUDE_CODE_OAUTH_TOKEN` — lokal mit `claude setup-token` auf einem Claude-Abonnement erstellt — den Lauf stattdessen über dieses Abonnement abrechnet. Setzen Sie genau eines; sind beide konfiguriert, entscheidet die Claude-Code-CLI über den Abrechnungsweg. + ## Modell und Argumentation Über das Backend hinaus optimieren zwei Repository-Variablen, _wie_ der Agent ausgeführt wird: @@ -190,10 +192,11 @@ Legen Sie diese in den **Einstellungen → Geheimnisse und Variablen → Actions ### Geheimnisse (nur zwei erforderlich) -| Geheimnis | Zweck | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | Widerruflicher Token, geprägt von `npx @agent-native/core@latest connect`. Autorisiert die Veröffentlichung des Zusammenfassungsplans und des Screenshot-Uploads. | -| `ANTHROPIC_API_KEY` | Der LLM-Schlüssel für das Standard-Claude-Code-Backend. | +| Geheimnis | Zweck | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | Widerruflicher Token, geprägt von `npx @agent-native/core@latest connect`. Autorisiert die Veröffentlichung des Zusammenfassungsplans und des Screenshot-Uploads. | +| `ANTHROPIC_API_KEY` | Der LLM-Schlüssel für das Standard-Claude-Code-Backend. | +| `CLAUDE_CODE_OAUTH_TOKEN` | Alternative zu `ANTHROPIC_API_KEY`: ein Claude-Abonnement-Token aus `claude setup-token`. Setzen Sie das eine oder das andere, nicht beides. | **Teams: Verwenden Sie ein Organisationsdienst-Token.** Ein persönliches Token ist an die Person gebunden Wer hat es geprägt – wenn sie die Organisation verlassen oder ihre Token widerrufen, jedes Repo mit @@ -494,6 +497,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/es-ES/pr-visual-recap.mdx b/packages/core/docs/content/locales/es-ES/pr-visual-recap.mdx index a340e6f92e..a130cdb11b 100644 --- a/packages/core/docs/content/locales/es-ES/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/es-ES/pr-visual-recap.mdx @@ -156,6 +156,8 @@ Elija qué agente de codificación ejecuta la habilidad con la variable de repos Si la variable no está configurada, la acción utiliza `claude`. +El backend `claude` se autentica con cualquiera de las dos credenciales: `ANTHROPIC_API_KEY` factura los tokens a su cuenta de la API de Anthropic, mientras que `CLAUDE_CODE_OAUTH_TOKEN` —generado localmente con `claude setup-token` en una suscripción de Claude— factura la ejecución a esa suscripción. Configure exactamente una; si ambas están configuradas, la vía de facturación será la que prefiera la CLI de Claude Code. + ## Modelo y razonamiento Más allá del backend, dos variables del repositorio ajustan _cómo_ se ejecuta el agente: @@ -190,10 +192,11 @@ Configúrelos en **Configuración → Secretos y variables → Actions** de su r ### Secretos (solo se requieren dos) -| Secreto | Propósito | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `PLAN_RECAP_TOKEN` | Token revocable acuñado por `npx @agent-native/core@latest connect`. Autoriza la publicación del plan de resumen y la carga de la captura de pantalla. | -| `ANTHROPIC_API_KEY` | La clave LLM para el backend predeterminado del código Claude. | +| Secreto | Propósito | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `PLAN_RECAP_TOKEN` | Token revocable acuñado por `npx @agent-native/core@latest connect`. Autoriza la publicación del plan de resumen y la carga de la captura de pantalla. | +| `ANTHROPIC_API_KEY` | La clave LLM para el backend predeterminado del código Claude. | +| `CLAUDE_CODE_OAUTH_TOKEN` | Alternativa a `ANTHROPIC_API_KEY`: un token de suscripción de Claude generado con `claude setup-token`. Configure una u otra, no ambas. | **Equipos: utilice un token de servicio de la organización.** Un token personal está vinculado a la persona quién lo acuñó: si abandonan la organización o revocan sus tokens, cada repositorio lo usará @@ -494,6 +497,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/fr-FR/pr-visual-recap.mdx b/packages/core/docs/content/locales/fr-FR/pr-visual-recap.mdx index c89f5ed1c3..cf56550b91 100644 --- a/packages/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/fr-FR/pr-visual-recap.mdx @@ -156,6 +156,8 @@ Choisissez quel agent de codage exécute la compétence avec la variable du réf Si la variable n'est pas définie, l'action utilise `claude`. +Le backend `claude` s'authentifie avec l'un ou l'autre identifiant : `ANTHROPIC_API_KEY` facture les jetons sur votre compte API Anthropic, tandis que `CLAUDE_CODE_OAUTH_TOKEN` — créé localement avec `claude setup-token` sur un abonnement Claude — impute l'exécution à cet abonnement. N'en définissez qu'un seul ; si les deux sont configurés, la voie de facturation dépend de celle que la CLI Claude Code privilégie. + ## Modèle et raisonnement Au-delà du backend, deux variables du référentiel ajustent _comment_ l'agent s'exécute : @@ -190,10 +192,11 @@ Définissez-les dans **Paramètres → Secrets et variables → Actions** de vot ### Secrets (seulement deux requis) -| Secret | Objectif | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | Jeton révocable émis par `npx @agent-native/core@latest connect`. Autorise la publication du plan récapitulatif et le téléchargement de la capture d'écran. | -| `ANTHROPIC_API_KEY` | La clé LLM pour le backend du code Claude par défaut. | +| Secret | Objectif | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | Jeton révocable émis par `npx @agent-native/core@latest connect`. Autorise la publication du plan récapitulatif et le téléchargement de la capture d'écran. | +| `ANTHROPIC_API_KEY` | La clé LLM pour le backend du code Claude par défaut. | +| `CLAUDE_CODE_OAUTH_TOKEN` | Alternative à `ANTHROPIC_API_KEY` : un jeton d'abonnement Claude créé avec `claude setup-token`. Définissez l'un ou l'autre, pas les deux. | **Équipes : utilisez un jeton de service d'organisation.** Un jeton personnel est lié à la personne qui l'a créé – s'il quitte l'organisation ou révoque ses jetons, chaque dépôt utilisant @@ -495,6 +498,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/hi-IN/pr-visual-recap.mdx b/packages/core/docs/content/locales/hi-IN/pr-visual-recap.mdx index af0e9abf6c..1db06af7f2 100644 --- a/packages/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/hi-IN/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor यदि वेरिएबल अनसेट है, तो कार्रवाई `claude` का उपयोग करती है। +`claude` बैकएंड इनमें से किसी भी क्रेडेंशियल से प्रमाणित होता है: `ANTHROPIC_API_KEY` टोकन का बिल आपके Anthropic API खाते में जोड़ता है, जबकि Claude सब्सक्रिप्शन पर `claude setup-token` से स्थानीय रूप से बनाया गया `CLAUDE_CODE_OAUTH_TOKEN` उस रन का बिल उसी सब्सक्रिप्शन में जोड़ता है। इनमें से ठीक एक ही सेट करें; दोनों कॉन्फ़िगर होने पर बिलिंग किस रास्ते जाएगी यह Claude Code CLI की पसंद पर निर्भर करता है। + ## मॉडल और तर्क बैकएंड से परे, दो रिपॉजिटरी वैरिएबल एजेंट को चलाने के तरीके को ट्यून करते हैं: @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### रहस्य (केवल दो आवश्यक) -| गुप्त | उद्देश्य | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect` द्वारा प्रतिसंहरणीय टोकन ढाला गया। पुनर्कथन योजना और स्क्रीनशॉट अपलोड को प्रकाशित करने के लिए अधिकृत करता है। | -| `ANTHROPIC_API_KEY` | डिफ़ॉल्ट Claude कोड बैकएंड के लिए LLM कुंजी। | +| गुप्त | उद्देश्य | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect` द्वारा प्रतिसंहरणीय टोकन ढाला गया। पुनर्कथन योजना और स्क्रीनशॉट अपलोड को प्रकाशित करने के लिए अधिकृत करता है। | +| `ANTHROPIC_API_KEY` | डिफ़ॉल्ट Claude कोड बैकएंड के लिए LLM कुंजी। | +| `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY` का विकल्प: `claude setup-token` से बनाया गया Claude सब्सक्रिप्शन टोकन। दोनों नहीं, कोई एक सेट करें। | **टीमें: एक संगठन सेवा टोकन का उपयोग करें।** एक व्यक्तिगत टोकन व्यक्ति से जुड़ा होता है इसे किसने बनाया - यदि वे संगठन छोड़ देते हैं या अपने टोकन रद्द कर देते हैं, तो प्रत्येक रेपो का उपयोग किया जाता है @@ -495,6 +498,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/ja-JP/pr-visual-recap.mdx b/packages/core/docs/content/locales/ja-JP/pr-visual-recap.mdx index fb10f550de..07f3b420b4 100644 --- a/packages/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/ja-JP/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor 変数が設定されていない場合、アクションは `claude` を使用します。 +`claude` バックエンドはどちらの資格情報でも認証できます。`ANTHROPIC_API_KEY` は Anthropic API アカウントにトークン料金が課金され、Claude サブスクリプションで `claude setup-token` を実行してローカルに発行した `CLAUDE_CODE_OAUTH_TOKEN` は実行分をそのサブスクリプションに課金します。どちらか一方だけを設定してください。両方を設定した場合、どちらで課金されるかは Claude Code CLI の選択次第になります。 + ## モデルと推論 バックエンドを超えて、2 つのリポジトリ変数がエージェントの実行方法を調整します。 @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### シークレット (必須は 2 つだけ) -| 秘密 | 目的 | -| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect` によって作成された取り消し可能なトークン。まとめ計画の公開とスクリーンショットのアップロードを承認します。 | -| `ANTHROPIC_API_KEY` | デフォルトの Claude コード バックエンドの LLM キー。 | +| 秘密 | 目的 | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect` によって作成された取り消し可能なトークン。まとめ計画の公開とスクリーンショットのアップロードを承認します。 | +| `ANTHROPIC_API_KEY` | デフォルトの Claude コード バックエンドの LLM キー。 | +| `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY` の代替。`claude setup-token` で発行する Claude サブスクリプションのトークン。どちらか一方のみを設定します。 | **Teams: 組織サービス トークンを使用します。** 個人トークンは個人にバインドされます 誰が作成したか — 彼らが組織を離れるかトークンを取り消した場合、すべてのリポジトリは @@ -492,6 +495,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/ko-KR/pr-visual-recap.mdx b/packages/core/docs/content/locales/ko-KR/pr-visual-recap.mdx index 7addef554b..ffcf1b6b2e 100644 --- a/packages/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/ko-KR/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor 변수가 설정되지 않은 경우 작업은 `claude`를 사용합니다. +`claude` 백엔드는 두 자격 증명 중 하나로 인증합니다. `ANTHROPIC_API_KEY`는 Anthropic API 계정에 토큰 요금을 청구하고, Claude 구독에서 `claude setup-token`으로 로컬에서 발급한 `CLAUDE_CODE_OAUTH_TOKEN`은 해당 실행을 그 구독에 청구합니다. 둘 중 하나만 설정하세요. 둘 다 설정하면 어느 쪽으로 청구될지는 Claude Code CLI의 선택에 따릅니다. + ## 모델 및 추론 백엔드 외에도 두 개의 저장소 변수가 에이전트 실행 *방법*을 조정합니다. @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### 비밀번호(2개만 필요) -| 비밀 | 목적 | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect`가 발행한 취소 가능한 토큰입니다. 요약 계획 게시 및 스크린샷 업로드를 승인합니다. | -| `ANTHROPIC_API_KEY` | 기본 Claude 코드 백엔드에 대한 LLM 키입니다. | +| 비밀 | 목적 | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `PLAN_RECAP_TOKEN` | `npx @agent-native/core@latest connect`가 발행한 취소 가능한 토큰입니다. 요약 계획 게시 및 스크린샷 업로드를 승인합니다. | +| `ANTHROPIC_API_KEY` | 기본 Claude 코드 백엔드에 대한 LLM 키입니다. | +| `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY`의 대안: `claude setup-token`으로 발급한 Claude 구독 토큰. 둘 중 하나만 설정합니다. | **팀: 조직 서비스 토큰을 사용합니다.** 개인 토큰은 개인에게 귀속됩니다. 만든 사람 - 조직을 떠나거나 토큰을 취소하면 모든 저장소는 @@ -494,6 +497,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/pt-BR/pr-visual-recap.mdx b/packages/core/docs/content/locales/pt-BR/pr-visual-recap.mdx index bc43d0044b..9af5ec2c43 100644 --- a/packages/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/pt-BR/pr-visual-recap.mdx @@ -156,6 +156,8 @@ Escolha qual agente de codificação executa a habilidade com a variável de rep Se a variável não estiver definida, a ação usará `claude`. +O backend `claude` se autentica com qualquer uma das credenciais: `ANTHROPIC_API_KEY` cobra os tokens na sua conta da API da Anthropic, enquanto `CLAUDE_CODE_OAUTH_TOKEN` — gerado localmente com `claude setup-token` em uma assinatura Claude — cobra a execução nessa assinatura. Defina exatamente uma; com as duas configuradas, o caminho de cobrança será o que a CLI do Claude Code preferir. + ## Modelo e raciocínio Além do back-end, duas variáveis de repositório ajustam _como_ o agente é executado: @@ -190,10 +192,11 @@ Defina-os em **Configurações → Segredos e variáveis → Actions** do seu re ### Segredos (são necessários apenas dois) -| Segredo | Propósito | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | Token revogável cunhado por `npx @agent-native/core@latest connect`. Autoriza a publicação do plano de recapitulação e do upload da captura de tela. | -| `ANTHROPIC_API_KEY` | A chave LLM para o back-end padrão do código Claude. | +| Segredo | Propósito | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | Token revogável cunhado por `npx @agent-native/core@latest connect`. Autoriza a publicação do plano de recapitulação e do upload da captura de tela. | +| `ANTHROPIC_API_KEY` | A chave LLM para o back-end padrão do código Claude. | +| `CLAUDE_CODE_OAUTH_TOKEN` | Alternativa ao `ANTHROPIC_API_KEY`: um token de assinatura Claude gerado com `claude setup-token`. Defina uma ou outra, não as duas. | **Equipes: use um token de serviço organizacional.** Um token pessoal está vinculado à pessoa quem o cunhou — se eles deixarem a organização ou revogarem seus tokens, todos os repositórios usarão @@ -494,6 +497,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/zh-CN/pr-visual-recap.mdx b/packages/core/docs/content/locales/zh-CN/pr-visual-recap.mdx index 93ed90a904..6b217ac388 100644 --- a/packages/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/zh-CN/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor 如果变量未设置,则操作使用 `claude`。 +`claude` 后端可使用其中任一凭据进行身份验证:`ANTHROPIC_API_KEY` 按令牌计入你的 Anthropic API 账户,而通过 `claude setup-token` 在 Claude 订阅上本地生成的 `CLAUDE_CODE_OAUTH_TOKEN` 则将本次运行计入该订阅。请只设置其中一个;如果两者都配置,计费方式取决于 Claude Code CLI 的选择。 + ## 模型和推理 除了后端之外,两个存储库变量还可以调整代理的运行方式: @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### 秘密(只需两个) -| 秘密 | 目的 | -| ------------------- | ----------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | 由 `npx @agent-native/core@latest connect` 铸造的可撤销代币。授权发布回顾计划和截图上传。 | -| `ANTHROPIC_API_KEY` | 默认 Claude 代码后端的 LLM 密钥。 | +| 秘密 | 目的 | +| ------------------------- | -------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | 由 `npx @agent-native/core@latest connect` 铸造的可撤销代币。授权发布回顾计划和截图上传。 | +| `ANTHROPIC_API_KEY` | 默认 Claude 代码后端的 LLM 密钥。 | +| `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY` 的替代方案:通过 `claude setup-token` 生成的 Claude 订阅令牌。二者只设其一,不要同时设置。 | **团队:使用组织服务令牌。**个人令牌与人员绑定 谁铸造了它 - 如果他们离开组织或撤销他们的代币,每个存储库都会使用 @@ -492,6 +495,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/locales/zh-TW/pr-visual-recap.mdx b/packages/core/docs/content/locales/zh-TW/pr-visual-recap.mdx index 24f43ba3ce..858e18dc75 100644 --- a/packages/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +++ b/packages/core/docs/content/locales/zh-TW/pr-visual-recap.mdx @@ -156,6 +156,8 @@ npx @agent-native/recap-cli@latest recap doctor 如果變數未設定,則操作使用 `claude`。 +`claude` 後端可使用其中任一憑證進行驗證:`ANTHROPIC_API_KEY` 會將權杖用量計入你的 Anthropic API 帳戶,而透過 `claude setup-token` 在 Claude 訂閱上於本機產生的 `CLAUDE_CODE_OAUTH_TOKEN` 則會將這次執行計入該訂閱。請只設定其中一個;若兩者都設定,計費方式取決於 Claude Code CLI 的選擇。 + ## 模型和推理 除了後端之外,兩個儲存庫變數還可以調整代理的執行方式: @@ -190,10 +192,11 @@ npx @agent-native/recap-cli@latest recap doctor ### 秘密(只需兩個) -| 秘密 | 目的 | -| ------------------- | ----------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | 由 `npx @agent-native/core@latest connect` 鑄造的可撤銷代幣。授權發布回顧計畫和截圖上傳。 | -| `ANTHROPIC_API_KEY` | 預設 Claude 程式碼後端的 LLM 金鑰。 | +| 秘密 | 目的 | +| ------------------------- | -------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | 由 `npx @agent-native/core@latest connect` 鑄造的可撤銷代幣。授權發布回顧計畫和截圖上傳。 | +| `ANTHROPIC_API_KEY` | 預設 Claude 程式碼後端的 LLM 金鑰。 | +| `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY` 的替代方案:以 `claude setup-token` 產生的 Claude 訂閱權杖。兩者擇一,不要同時設定。 | **團隊:使用組織服務權杖。**個人權杖與人員綁定 誰鑄造了它 - 如果他們離開組織或撤銷他們的代幣,每個儲存庫都會使用 @@ -492,6 +495,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} with: diff --git a/packages/core/docs/content/pr-visual-recap.mdx b/packages/core/docs/content/pr-visual-recap.mdx index 100a5e2203..54ebb1c449 100644 --- a/packages/core/docs/content/pr-visual-recap.mdx +++ b/packages/core/docs/content/pr-visual-recap.mdx @@ -174,14 +174,16 @@ runner. Choose which coding agent runs the skill with the `VISUAL_RECAP_AGENT` repository variable: -| `VISUAL_RECAP_AGENT` | Coding agent | Required API key | Required variables | -| -------------------- | --------------------------------------- | ---------------------- | --------------------------------------------- | -| `claude` _(default)_ | Claude Code CLI | `ANTHROPIC_API_KEY` | — | -| `codex` | OpenAI Codex CLI | `OPENAI_API_KEY` | — | -| `openai-compatible` | Agent-Native Code + compatible provider | `VISUAL_RECAP_API_KEY` | `VISUAL_RECAP_BASE_URL`, `VISUAL_RECAP_MODEL` | +| `VISUAL_RECAP_AGENT` | Coding agent | Required API key | Required variables | +| -------------------- | --------------------------------------- | ------------------------------------------------ | --------------------------------------------- | +| `claude` _(default)_ | Claude Code CLI | `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` | — | +| `codex` | OpenAI Codex CLI | `OPENAI_API_KEY` | — | +| `openai-compatible` | Agent-Native Code + compatible provider | `VISUAL_RECAP_API_KEY` | `VISUAL_RECAP_BASE_URL`, `VISUAL_RECAP_MODEL` | If the variable is unset, the action uses `claude`. +The `claude` backend authenticates with either credential: `ANTHROPIC_API_KEY` bills tokens to your Anthropic API account, while `CLAUDE_CODE_OAUTH_TOKEN` — minted locally with `claude setup-token` on a Claude subscription — bills the run to that subscription instead. Set exactly one; with both configured the billing path is whichever the Claude Code CLI prefers. + ## Model and reasoning Beyond the backend, these repository variables tune how the agent runs: @@ -220,10 +222,11 @@ Set these in your repository's **Settings → Secrets and variables → Actions* ### Secrets for the default backend -| Secret | Purpose | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `PLAN_RECAP_TOKEN` | Revocable token minted by `npx @agent-native/core@latest connect`. Authorizes publishing the recap plan and the screenshot upload. | -| `ANTHROPIC_API_KEY` | The LLM key for the default Claude Code backend. | +| Secret | Purpose | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `PLAN_RECAP_TOKEN` | Revocable token minted by `npx @agent-native/core@latest connect`. Authorizes publishing the recap plan and the screenshot upload. | +| `ANTHROPIC_API_KEY` | The LLM key for the default Claude Code backend. | +| `CLAUDE_CODE_OAUTH_TOKEN` | Alternative to `ANTHROPIC_API_KEY`: a Claude subscription token from `claude setup-token`. Set one or the other, not both. | **Teams: use an org service token.** A personal token is bound to the person who minted it — if they leave the org or revoke their tokens, every repo using @@ -527,6 +530,7 @@ jobs: secrets: PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} VISUAL_RECAP_API_KEY: ${{ secrets.VISUAL_RECAP_API_KEY }} PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL }} diff --git a/packages/core/src/cli/recap.spec.ts b/packages/core/src/cli/recap.spec.ts index c017b5f128..e87c92b8e0 100644 --- a/packages/core/src/cli/recap.spec.ts +++ b/packages/core/src/cli/recap.spec.ts @@ -1001,16 +1001,16 @@ describe("recap setup planning", () => { it("selects required secrets for each backend", () => { expect(recapRequiredSecrets("claude")).toEqual([ - "PLAN_RECAP_TOKEN", - "ANTHROPIC_API_KEY", + { names: ["PLAN_RECAP_TOKEN"] }, + { names: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"] }, ]); expect(recapRequiredSecrets("codex")).toEqual([ - "PLAN_RECAP_TOKEN", - "OPENAI_API_KEY", + { names: ["PLAN_RECAP_TOKEN"] }, + { names: ["OPENAI_API_KEY"] }, ]); expect(recapRequiredSecrets("openai-compatible")).toEqual([ - "PLAN_RECAP_TOKEN", - "VISUAL_RECAP_API_KEY", + { names: ["PLAN_RECAP_TOKEN"] }, + { names: ["VISUAL_RECAP_API_KEY"] }, ]); }); @@ -1109,7 +1109,10 @@ describe("recap setup planning", () => { repo: "BuilderIO/example", workflowPath: path.join(".github", "workflows", "pr-visual-recap.yml"), workflowExists: true, - requiredSecrets: ["PLAN_RECAP_TOKEN", "OPENAI_API_KEY"], + requiredSecrets: [ + { names: ["PLAN_RECAP_TOKEN"] }, + { names: ["OPENAI_API_KEY"] }, + ], variableValues: { VISUAL_RECAP_AGENT: "codex", VISUAL_RECAP_MODEL: "gpt-5.6-sol", @@ -1144,8 +1147,8 @@ describe("recap setup planning", () => { expect(plan.agent).toBe("openai-compatible"); expect(plan.requiredSecrets).toEqual([ - "PLAN_RECAP_TOKEN", - "VISUAL_RECAP_API_KEY", + { names: ["PLAN_RECAP_TOKEN"] }, + { names: ["VISUAL_RECAP_API_KEY"] }, ]); expect(plan.requiredVariables).toEqual([ { @@ -2441,14 +2444,24 @@ describe("recap gate decision", () => { expect(result.reasons).toContain("PLAN_RECAP_TOKEN not configured"); }); - it("skips when the claude backend's ANTHROPIC_API_KEY is missing", () => { - const result = evaluateRecapGate(ok({ hasAnthropic: false })); + it("skips when the claude backend has neither credential", () => { + const result = evaluateRecapGate( + ok({ hasAnthropic: false, hasClaudeOauth: false }), + ); expect(result.run).toBe(false); expect(result.reasons).toContain( - "ANTHROPIC_API_KEY not configured (claude backend)", + "neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)", ); }); + it("runs the claude backend on a subscription OAuth token alone", () => { + const result = evaluateRecapGate( + ok({ hasAnthropic: false, hasClaudeOauth: true }), + ); + expect(result.run).toBe(true); + expect(result.reasons).toEqual([]); + }); + it("skips when the codex backend's OPENAI_API_KEY is missing", () => { const result = evaluateRecapGate( ok({ agentRaw: "codex", hasOpenai: false }), diff --git a/packages/recap-cli/src/pr-visual-recap-workflow.spec.ts b/packages/recap-cli/src/pr-visual-recap-workflow.spec.ts index 41168bf91f..3ac4035802 100644 --- a/packages/recap-cli/src/pr-visual-recap-workflow.spec.ts +++ b/packages/recap-cli/src/pr-visual-recap-workflow.spec.ts @@ -31,6 +31,22 @@ describe("the recap installer workflow", () => { ); }); + // A caller that passes a secret the reusable contract does not declare fails + // the whole run at startup, so the template and the contract move together. + it("lets reusable callers pass a Claude subscription token", () => { + const reusable = readFileSync( + path.join(repoRoot, ".github/workflows/pr-visual-recap-reusable.yml"), + "utf8", + ); + + expect(reusable).toContain( + " CLAUDE_CODE_OAUTH_TOKEN:\n required: false", + ); + expect(buildReusableCallerWorkflow()).toContain( + "CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}", + ); + }); + it("wakes labeled events when reusable callers configure labels directly", () => { const workflow = buildReusableCallerWorkflow({ requiredLabels: "visual recap", diff --git a/packages/recap-cli/src/recap-gate.spec.ts b/packages/recap-cli/src/recap-gate.spec.ts index eb9d9e041c..be60991549 100644 --- a/packages/recap-cli/src/recap-gate.spec.ts +++ b/packages/recap-cli/src/recap-gate.spec.ts @@ -59,4 +59,24 @@ describe("evaluateRecapGate", () => { expect(decision.run).toBe(true); expect(decision.reasons).toEqual([]); }); + + it("runs the claude backend on a subscription OAuth token alone", () => { + const decision = evaluateRecapGate( + validGateInput({ hasAnthropic: false, hasClaudeOauth: true }), + ); + + expect(decision.run).toBe(true); + expect(decision.reasons).toEqual([]); + }); + + it("skips the claude backend when neither credential is configured", () => { + const decision = evaluateRecapGate( + validGateInput({ hasAnthropic: false, hasClaudeOauth: false }), + ); + + expect(decision.run).toBe(false); + expect(decision.reasons).toContain( + "neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)", + ); + }); }); diff --git a/packages/recap-cli/src/recap.ts b/packages/recap-cli/src/recap.ts index 84e251af52..440528a06d 100644 --- a/packages/recap-cli/src/recap.ts +++ b/packages/recap-cli/src/recap.ts @@ -103,6 +103,7 @@ export const PR_VISUAL_RECAP_SETUP: string[] = [ " PLAN_RECAP_TOKEN — bearer token from `npx @agent-native/core@latest connect`", " ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend", "Optional (only if you change defaults):", + " CLAUDE_CODE_OAUTH_TOKEN (secret) — instead of ANTHROPIC_API_KEY, bill the claude backend to a Claude subscription; mint it with `claude setup-token` and set exactly one of the two", " OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude", " VISUAL_RECAP_API_KEY (secret) + VISUAL_RECAP_AGENT=openai-compatible + VISUAL_RECAP_BASE_URL (variable) — use DeepSeek, Kimi, or any OpenAI-compatible API", " VISUAL_RECAP_MODEL (variable, required for openai-compatible) — provider model id; optional override for Claude/Codex", @@ -230,6 +231,7 @@ export function buildReusableCallerWorkflow( ` secrets:\n` + ` PLAN_RECAP_TOKEN: \${{ secrets.PLAN_RECAP_TOKEN }}\n` + ` ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}\n` + + ` CLAUDE_CODE_OAUTH_TOKEN: \${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}\n` + ` OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}\n` + ` VISUAL_RECAP_API_KEY: \${{ secrets.VISUAL_RECAP_API_KEY }}\n` + ` PLAN_RECAP_APP_URL: \${{ secrets.PLAN_RECAP_APP_URL }}\n` + @@ -321,14 +323,18 @@ export function normalizeRecapAgent(value: string | undefined): RecapAgent { ); } -export function recapRequiredSecrets(agent: RecapAgent): string[] { +export function recapRequiredSecrets( + agent: RecapAgent, +): RecapSecretRequirement[] { return [ - "PLAN_RECAP_TOKEN", + { names: ["PLAN_RECAP_TOKEN"] }, agent === "codex" - ? "OPENAI_API_KEY" + ? { names: ["OPENAI_API_KEY"] } : agent === "openai-compatible" - ? "VISUAL_RECAP_API_KEY" - : "ANTHROPIC_API_KEY", + ? { names: ["VISUAL_RECAP_API_KEY"] } + : // Either credential authenticates the Claude Code CLI: the API key + // bills tokens, the OAuth token bills a Claude subscription. + { names: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"] }, ]; } @@ -578,13 +584,23 @@ export interface RecapSetupPlan { repo?: string; workflowPath: string; workflowExists: boolean; - requiredSecrets: string[]; + requiredSecrets: RecapSecretRequirement[]; requiredVariables: readonly RecapVariableRequirement[]; variableProblems: RecapVariableProblem[]; variableValues: Record; secretValues: Record; } +/** + * One credential the workflow needs. `names` holds interchangeable secrets in + * the order `recap setup` prefers them — the claude backend authenticates with + * either an API key or a subscription OAuth token, so demanding a specific one + * would report a working repo as misconfigured. + */ +export interface RecapSecretRequirement { + names: [string, ...string[]]; +} + export interface RecapVariableRequirement { name: | "VISUAL_RECAP_BASE_URL" @@ -777,12 +793,21 @@ export function buildRecapSetupPlan(input: { : []; const planToken = envValue(env, "PLAN_RECAP_TOKEN") ?? planTokenFromLocalStore(appUrl); - const llmSecretName = - agent === "codex" - ? "OPENAI_API_KEY" - : agent === "openai-compatible" - ? "VISUAL_RECAP_API_KEY" - : "ANTHROPIC_API_KEY"; + // Take the first name each requirement has a local value for, so setup pushes + // one credential per requirement instead of also writing an empty alternative. + const secretValues: Record = { + PLAN_RECAP_TOKEN: planToken, + PLAN_RECAP_APP_URL: appUrl === DEFAULT_RECAP_APP_URL ? undefined : appUrl, + }; + for (const requirement of requiredSecrets) { + for (const name of requirement.names) { + const value = envValue(env, name); + if (value) { + secretValues[name] = value; + break; + } + } + } const variableValues: Record = {}; if (agent !== "claude") variableValues.VISUAL_RECAP_AGENT = agent; for (const key of [ @@ -837,11 +862,7 @@ export function buildRecapSetupPlan(input: { requiredVariables, variableProblems, variableValues, - secretValues: { - PLAN_RECAP_TOKEN: planToken, - [llmSecretName]: envValue(env, llmSecretName), - PLAN_RECAP_APP_URL: appUrl === DEFAULT_RECAP_APP_URL ? undefined : appUrl, - }, + secretValues, }; } @@ -941,11 +962,16 @@ function runSetup(args: Record): void { } else { lines.push(""); lines.push("GitHub secrets/variables:"); - const secretNames = [ + const secretRequirements: RecapSecretRequirement[] = [ ...plan.requiredSecrets, - ...(plan.secretValues.PLAN_RECAP_APP_URL ? ["PLAN_RECAP_APP_URL"] : []), + ...(plan.secretValues.PLAN_RECAP_APP_URL + ? [{ names: ["PLAN_RECAP_APP_URL"] as [string] }] + : []), ]; - for (const name of secretNames) { + for (const requirement of secretRequirements) { + const name = + requirement.names.find((candidate) => plan.secretValues[candidate]) ?? + requirement.names[0]; const status = setGithubSecret( name, plan.secretValues[name], @@ -966,6 +992,11 @@ function runSetup(args: Record): void { lines.push( ` Or set manually: ${commandForMissingSecret(name, repo)}`, ); + for (const alternative of requirement.names.slice(1)) { + lines.push( + ` Alternative: ${commandForMissingSecret(alternative, repo)}`, + ); + } } else { lines.push(` ${name}: could not set with gh.`); lines.push(` Set manually: ${commandForMissingSecret(name, repo)}`); @@ -1085,13 +1116,26 @@ function runDoctor(args: Record): void { lines.push("[missing] Could not read GitHub Actions secrets with gh."); lines.push(" Run gh auth status, or pass --repo owner/name."); } else { - for (const name of plan.requiredSecrets) { - if (secretNames.has(name)) { - lines.push(`[ok] GitHub secret configured: ${name}.`); - } else { + for (const requirement of plan.requiredSecrets) { + const configured = requirement.names.filter((name) => + secretNames.has(name), + ); + if (configured.length === 0) { ok = false; - lines.push(`[missing] GitHub secret missing: ${name}.`); - lines.push(` Set it with: ${commandForMissingSecret(name, repo)}`); + lines.push( + `[missing] GitHub secret missing: ${requirement.names.join(" or ")}.`, + ); + lines.push( + ` Set it with: ${commandForMissingSecret(requirement.names[0], repo)}`, + ); + continue; + } + lines.push(`[ok] GitHub secret configured: ${configured.join(", ")}.`); + if (configured.length > 1) { + lines.push( + `[warn] Interchangeable credentials both configured: ${configured.join(", ")}.`, + ); + lines.push(" Remove all but one so the billing path is unambiguous."); } } } @@ -1383,6 +1427,10 @@ export function sanitizeAgentFailureSummary( ) .replace(/PLAN_RECAP_TOKEN=([^\s]+)/g, "PLAN_RECAP_TOKEN=[redacted]") .replace(/ANTHROPIC_API_KEY=([^\s]+)/g, "ANTHROPIC_API_KEY=[redacted]") + .replace( + /CLAUDE_CODE_OAUTH_TOKEN=([^\s]+)/g, + "CLAUDE_CODE_OAUTH_TOKEN=[redacted]", + ) .replace(/OPENAI_API_KEY=([^\s]+)/g, "OPENAI_API_KEY=[redacted]"); const sanitizedLines = value @@ -4015,6 +4063,8 @@ export interface RecapGateInput { hasPlan: boolean; /** ANTHROPIC_API_KEY present. */ hasAnthropic: boolean; + /** CLAUDE_CODE_OAUTH_TOKEN present — the subscription-billed claude credential. */ + hasClaudeOauth?: boolean; /** OPENAI_API_KEY present. */ hasOpenai: boolean; /** VISUAL_RECAP_API_KEY present for OpenAI-compatible backends. */ @@ -4165,8 +4215,10 @@ export function evaluateRecapGate(input: RecapGateInput): { if (!input.hasOpenai) reasons.push("OPENAI_API_KEY not configured (codex backend)"); } else if (agent === "claude") { - if (!input.hasAnthropic) - reasons.push("ANTHROPIC_API_KEY not configured (claude backend)"); + if (!input.hasAnthropic && !input.hasClaudeOauth) + reasons.push( + "neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN configured (claude backend)", + ); } else { if (!input.hasOpenaiCompatible) reasons.push( @@ -4324,6 +4376,7 @@ async function runGate(): Promise { repositoryPrivate, hasPlan: process.env.HAS_PLAN === "true", hasAnthropic: process.env.HAS_ANTHROPIC === "true", + hasClaudeOauth: process.env.HAS_CLAUDE_OAUTH === "true", hasOpenai: process.env.HAS_OPENAI === "true", hasOpenaiCompatible: process.env.HAS_COMPATIBLE === "true", agentRaw: process.env.AGENT,