Implement claude max auth

This commit is contained in:
Mark Wylde
2025-05-31 10:14:51 +01:00
parent 90c7a171fc
commit 0a1983379e
3 changed files with 87 additions and 12 deletions

View File

@@ -42,7 +42,10 @@ inputs:
# Auth configuration # Auth configuration
anthropic_api_key: anthropic_api_key:
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)" description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex). Set to 'use-oauth' when using claude_credentials"
required: false
claude_credentials:
description: "Claude OAuth credentials JSON for Claude AI Max subscription authentication"
required: false required: false
gitea_token: gitea_token:
description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)" description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)"
@@ -96,6 +99,8 @@ runs:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ID: ${{ github.run_id }}
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }} GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CREDENTIALS: ${{ inputs.claude_credentials }}
- name: Run Claude Code - name: Run Claude Code
id: claude-code id: claude-code
@@ -123,6 +128,7 @@ runs:
USE_BEDROCK: ${{ inputs.use_bedrock }} USE_BEDROCK: ${{ inputs.use_bedrock }}
USE_VERTEX: ${{ inputs.use_vertex }} USE_VERTEX: ${{ inputs.use_vertex }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CREDENTIALS: ${{ inputs.claude_credentials }}
# GitHub token for repository access # GitHub token for repository access
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}

59
src/claude/oauth-setup.ts Normal file
View File

@@ -0,0 +1,59 @@
import { mkdir, writeFile } from "fs/promises";
import { join } from "path";
import { homedir } from "os";
interface OAuthCredentials {
accessToken: string;
refreshToken: string;
expiresAt: string;
}
interface ClaudeCredentialsInput {
claudeAiOauth: {
accessToken: string;
refreshToken: string;
expiresAt: number;
scopes: string[];
};
}
export async function setupOAuthCredentials(credentialsJson: string) {
try {
// Parse the credentials JSON
const parsedCredentials: ClaudeCredentialsInput = JSON.parse(credentialsJson);
if (!parsedCredentials.claudeAiOauth) {
throw new Error("Invalid credentials format: missing claudeAiOauth");
}
const { accessToken, refreshToken, expiresAt } = parsedCredentials.claudeAiOauth;
if (!accessToken || !refreshToken || !expiresAt) {
throw new Error("Invalid credentials format: missing required OAuth fields");
}
const claudeDir = join(homedir(), ".claude");
const credentialsPath = join(claudeDir, ".credentials.json");
// Create the .claude directory if it doesn't exist
await mkdir(claudeDir, { recursive: true });
// Create the credentials JSON structure
const credentialsData = {
claudeAiOauth: {
accessToken,
refreshToken,
expiresAt,
scopes: ["user:inference", "user:profile"],
},
};
// Write the credentials file
await writeFile(credentialsPath, JSON.stringify(credentialsData, null, 2));
console.log(`OAuth credentials written to ${credentialsPath}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to setup OAuth credentials: ${errorMessage}`);
}
}

View File

@@ -18,17 +18,27 @@ import { createPrompt } from "../create-prompt";
import { createClient } from "../github/api/client"; import { createClient } from "../github/api/client";
import { fetchGitHubData } from "../github/data/fetcher"; import { fetchGitHubData } from "../github/data/fetcher";
import { parseGitHubContext } from "../github/context"; import { parseGitHubContext } from "../github/context";
import { setupOAuthCredentials } from "../claude/oauth-setup";
async function run() { async function run() {
try { try {
// Step 1: Setup GitHub token // Step 1: Setup OAuth credentials if provided
const claudeCredentials = process.env.CLAUDE_CREDENTIALS;
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
if (claudeCredentials && anthropicApiKey === "use-oauth") {
await setupOAuthCredentials(claudeCredentials);
console.log("OAuth credentials configured for Claude AI Max subscription");
}
// Step 2: Setup GitHub token
const githubToken = await setupGitHubToken(); const githubToken = await setupGitHubToken();
const client = createClient(githubToken); const client = createClient(githubToken);
// Step 2: Parse GitHub context (once for all operations) // Step 3: Parse GitHub context (once for all operations)
const context = parseGitHubContext(); const context = parseGitHubContext();
// Step 3: Check write permissions // Step 4: Check write permissions
const hasWritePermissions = await checkWritePermissions( const hasWritePermissions = await checkWritePermissions(
client.api, client.api,
context, context,
@@ -39,7 +49,7 @@ async function run() {
); );
} }
// Step 4: Check trigger conditions // Step 5: Check trigger conditions
const containsTrigger = await checkTriggerAction(context); const containsTrigger = await checkTriggerAction(context);
// Set outputs that are always needed // Set outputs that are always needed
@@ -51,14 +61,14 @@ async function run() {
return; return;
} }
// Step 5: Check if actor is human // Step 6: Check if actor is human
await checkHumanActor(client.api, context); await checkHumanActor(client.api, context);
// Step 6: Create initial tracking comment // Step 7: Create initial tracking comment
const commentId = await createInitialComment(client.api, context); const commentId = await createInitialComment(client.api, context);
core.setOutput("claude_comment_id", commentId.toString()); core.setOutput("claude_comment_id", commentId.toString());
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation) // Step 8: Fetch GitHub data (once for both branch setup and prompt creation)
const githubData = await fetchGitHubData({ const githubData = await fetchGitHubData({
client: client, client: client,
repository: `${context.repository.owner}/${context.repository.repo}`, repository: `${context.repository.owner}/${context.repository.repo}`,
@@ -66,14 +76,14 @@ async function run() {
isPR: context.isPR, isPR: context.isPR,
}); });
// Step 8: Setup branch // Step 9: Setup branch
const branchInfo = await setupBranch(client, githubData, context); const branchInfo = await setupBranch(client, githubData, context);
core.setOutput("BASE_BRANCH", branchInfo.baseBranch); core.setOutput("BASE_BRANCH", branchInfo.baseBranch);
if (branchInfo.claudeBranch) { if (branchInfo.claudeBranch) {
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch); core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
} }
// Step 9: Update initial comment with branch link (only if a claude branch was created) // Step 10: Update initial comment with branch link (only if a claude branch was created)
if (branchInfo.claudeBranch) { if (branchInfo.claudeBranch) {
await updateTrackingComment( await updateTrackingComment(
client, client,
@@ -83,7 +93,7 @@ async function run() {
); );
} }
// Step 10: Create prompt file // Step 11: Create prompt file
await createPrompt( await createPrompt(
commentId, commentId,
branchInfo.baseBranch, branchInfo.baseBranch,
@@ -92,7 +102,7 @@ async function run() {
context, context,
); );
// Step 11: Get MCP configuration // Step 12: Get MCP configuration
const mcpConfig = await prepareMcpConfig( const mcpConfig = await prepareMcpConfig(
githubToken, githubToken,
context.repository.owner, context.repository.owner,