Implement claude max auth
This commit is contained in:
@@ -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
59
src/claude/oauth-setup.ts
Normal 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user