mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-01-29 11:23:31 +08:00
Feature/cca v1 refresh (#9)
Co-authored-by: Oleg Zaimkin <oleg.zaimkin@developertools.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
|
||||
**/.claude/settings.local.json
|
||||
|
||||
75
README.md
75
README.md
@@ -19,7 +19,7 @@ A Gitea action that provides a general-purpose [Claude Code](https://claude.ai/c
|
||||
|
||||
**Requirements**: You must be a repository admin to complete these steps.
|
||||
|
||||
1. Add `ANTHROPIC_API_KEY` or `CLAUDE_CREDENTIALS` to your repository secrets
|
||||
1. Add `ANTHROPIC_API_KEY` to your repository secrets
|
||||
2. Add `GITEA_TOKEN` to your repository secrets (a personal access token with repository read/write permissions)
|
||||
3. Copy the workflow file from [`examples/gitea-claude.yml`](./examples/gitea-claude.yml) into your repository's `.gitea/workflows/`
|
||||
|
||||
@@ -47,7 +47,6 @@ jobs:
|
||||
- uses: markwylde/claude-code-gitea-action@v1.0.5
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # if you want to use direct API
|
||||
claude_credentials: ${{ secrets.CLAUDE_CREDENTIALS }} # if you have a Claude Max subscription
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }} # could be another users token (specific Claude user?)
|
||||
claude_git_name: Claude # optional
|
||||
claude_git_email: claude@anthropic.com # optional
|
||||
@@ -57,8 +56,8 @@ jobs:
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------- |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex). Set to 'use-oauth' when using claude_credentials | No\* | - |
|
||||
| `claude_credentials` | Claude OAuth credentials JSON for Claude AI Max subscription authentication | No | - |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
|
||||
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No | - |
|
||||
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
|
||||
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
||||
| `gitea_token` | Gitea token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||
@@ -78,45 +77,6 @@ jobs:
|
||||
|
||||
> **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration.
|
||||
|
||||
## Claude Max Authentication
|
||||
|
||||
This action supports authentication using Claude Max OAuth credentials. This allows users with Claude Max subscriptions to use their existing authentication.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Get OAuth Credentials**: Use Claude Code to generate OAuth credentials:
|
||||
|
||||
```
|
||||
/auth-setup
|
||||
```
|
||||
|
||||
2. **Add Credentials to Repository**: Add the generated JSON credentials as a repository secret named `CLAUDE_CREDENTIALS`.
|
||||
|
||||
It should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"claudeAiOauth": {
|
||||
"accessToken": "sk-ant-xxx",
|
||||
"refreshToken": "sk-ant-xxx",
|
||||
"expiresAt": 1748707000000,
|
||||
"scopes": ["user:inference", "user:profile"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Configure Workflow**: Set up your workflow to use OAuth authentication:
|
||||
|
||||
```yaml
|
||||
- uses: markwylde/claude-code-gitea-action@v1.0.5
|
||||
with:
|
||||
anthropic_api_key: "use-oauth"
|
||||
claude_credentials: ${{ secrets.CLAUDE_CREDENTIALS }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
```
|
||||
|
||||
When `anthropic_api_key` is set to `'use-oauth'`, the action will use the OAuth credentials provided in `claude_credentials` instead of a direct API key.
|
||||
|
||||
## Gitea Configuration
|
||||
|
||||
This action has been enhanced to work with Gitea installations. The main differences from GitHub are:
|
||||
@@ -311,10 +271,33 @@ Use a specific Claude model:
|
||||
|
||||
## Cloud Providers
|
||||
|
||||
You can authenticate with Claude using any of these three methods:
|
||||
You can authenticate with Claude using any of these methods:
|
||||
|
||||
1. Direct Anthropic API (default)
|
||||
2. Anthropic OAuth credentials (Claude Max subscription)
|
||||
1. **Direct Anthropic API** (default) - Use your Anthropic API key
|
||||
2. **Claude Code OAuth Token** - Use OAuth token from Claude Code application
|
||||
|
||||
### Using Claude Code OAuth Token
|
||||
|
||||
If you have access to [Claude Code](https://claude.ai/code), you can use OAuth authentication instead of an API key:
|
||||
|
||||
1. **Generate OAuth Token**: run the following command and follow instructions:
|
||||
```
|
||||
claude setup-token
|
||||
```
|
||||
This will generate an OAuth token that you can use for authentication.
|
||||
|
||||
2. **Add Token to Repository**: Add the generated token as a repository secret named `CLAUDE_CODE_OAUTH_TOKEN`.
|
||||
|
||||
3. **Configure Workflow**: Use the OAuth token in your workflow:
|
||||
|
||||
```yaml
|
||||
- uses: markwylde/claude-code-gitea-action@v1.0.5
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
```
|
||||
|
||||
When `claude_code_oauth_token` is provided, it will be used instead of `anthropic_api_key` for authentication.
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
70
action.yml
70
action.yml
@@ -40,13 +40,36 @@ inputs:
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# New Claude Code settings
|
||||
settings:
|
||||
description: "Path to Claude Code settings JSON file, or settings JSON string"
|
||||
required: false
|
||||
default: ""
|
||||
system_prompt:
|
||||
description: "Override system prompt"
|
||||
required: false
|
||||
default: ""
|
||||
append_system_prompt:
|
||||
description: "Append to system prompt"
|
||||
required: false
|
||||
default: ""
|
||||
claude_env:
|
||||
description: "Custom environment variables to pass to Claude Code execution (YAML multiline format)"
|
||||
required: false
|
||||
default: ""
|
||||
fallback_model:
|
||||
description: "Enable automatic fallback to specified model when default model is overloaded"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Auth configuration
|
||||
anthropic_api_key:
|
||||
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex). Set to 'use-oauth' when using claude_credentials"
|
||||
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)"
|
||||
required: false
|
||||
claude_credentials:
|
||||
description: "Claude OAuth credentials JSON for Claude AI Max subscription authentication"
|
||||
claude_code_oauth_token:
|
||||
description: "Claude Code OAuth token (alternative to anthropic_api_key)"
|
||||
required: false
|
||||
default: ""
|
||||
gitea_token:
|
||||
description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)"
|
||||
required: false
|
||||
@@ -58,6 +81,10 @@ inputs:
|
||||
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
|
||||
required: false
|
||||
default: "false"
|
||||
use_node_cache:
|
||||
description: "Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files)"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
timeout_minutes:
|
||||
description: "Timeout in minutes for execution"
|
||||
@@ -108,12 +135,28 @@ runs:
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
|
||||
CLAUDE_CREDENTIALS: ${{ inputs.claude_credentials }}
|
||||
|
||||
- name: Install Claude
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
# Install Claude Code if no custom executable is provided
|
||||
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
||||
echo "Installing Claude Code..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.117
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
else
|
||||
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
||||
# Add the directory containing the custom executable to PATH
|
||||
CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}")
|
||||
echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
|
||||
fi
|
||||
# TODO pass claude_code_executable as input and use it here
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@v0.0.24
|
||||
uses: anthropics/claude-code-base-action@v0.0.63
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
@@ -124,6 +167,13 @@ runs:
|
||||
use_bedrock: ${{ inputs.use_bedrock }}
|
||||
use_vertex: ${{ inputs.use_vertex }}
|
||||
anthropic_api_key: ${{ inputs.anthropic_api_key }}
|
||||
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
|
||||
settings: ${{ inputs.settings }}
|
||||
system_prompt: ${{ inputs.system_prompt }}
|
||||
append_system_prompt: ${{ inputs.append_system_prompt }}
|
||||
claude_env: ${{ inputs.claude_env }}
|
||||
fallback_model: ${{ inputs.fallback_model }}
|
||||
use_node_cache: ${{ inputs.use_node_cache }}
|
||||
env:
|
||||
# Core configuration
|
||||
PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt
|
||||
@@ -136,7 +186,15 @@ runs:
|
||||
USE_BEDROCK: ${{ inputs.use_bedrock }}
|
||||
USE_VERTEX: ${{ inputs.use_vertex }}
|
||||
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
|
||||
CLAUDE_CREDENTIALS: ${{ inputs.claude_credentials }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
|
||||
|
||||
# New settings support
|
||||
SETTINGS: ${{ inputs.settings }}
|
||||
SYSTEM_PROMPT: ${{ inputs.system_prompt }}
|
||||
APPEND_SYSTEM_PROMPT: ${{ inputs.append_system_prompt }}
|
||||
CLAUDE_ENV: ${{ inputs.claude_env }}
|
||||
FALLBACK_MODEL: ${{ inputs.fallback_model }}
|
||||
USE_NODE_CACHE: ${{ inputs.use_node_cache }}
|
||||
|
||||
# GitHub token for repository access
|
||||
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
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,29 +18,17 @@ import { createPrompt } from "../create-prompt";
|
||||
import { createClient } from "../github/api/client";
|
||||
import { fetchGitHubData } from "../github/data/fetcher";
|
||||
import { parseGitHubContext } from "../github/context";
|
||||
import { setupOAuthCredentials } from "../claude/oauth-setup";
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// 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
|
||||
// Step 1: Setup GitHub token
|
||||
const githubToken = await setupGitHubToken();
|
||||
const client = createClient(githubToken);
|
||||
|
||||
// Step 3: Parse GitHub context (once for all operations)
|
||||
// Step 2: Parse GitHub context (once for all operations)
|
||||
const context = parseGitHubContext();
|
||||
|
||||
// Step 4: Check write permissions
|
||||
// Step 3: Check write permissions
|
||||
const hasWritePermissions = await checkWritePermissions(
|
||||
client.api,
|
||||
context,
|
||||
@@ -51,7 +39,7 @@ async function run() {
|
||||
);
|
||||
}
|
||||
|
||||
// Step 5: Check trigger conditions
|
||||
// Step 4: Check trigger conditions
|
||||
const containsTrigger = await checkTriggerAction(context);
|
||||
|
||||
// Set outputs that are always needed
|
||||
@@ -63,14 +51,14 @@ async function run() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 6: Check if actor is human
|
||||
// Step 5: Check if actor is human
|
||||
await checkHumanActor(client.api, context);
|
||||
|
||||
// Step 7: Create initial tracking comment
|
||||
// Step 6: Create initial tracking comment
|
||||
const commentId = await createInitialComment(client.api, context);
|
||||
core.setOutput("claude_comment_id", commentId.toString());
|
||||
|
||||
// Step 8: Fetch GitHub data (once for both branch setup and prompt creation)
|
||||
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
|
||||
const githubData = await fetchGitHubData({
|
||||
client: client,
|
||||
repository: `${context.repository.owner}/${context.repository.repo}`,
|
||||
@@ -78,14 +66,14 @@ async function run() {
|
||||
isPR: context.isPR,
|
||||
});
|
||||
|
||||
// Step 9: Setup branch
|
||||
// Step 8: Setup branch
|
||||
const branchInfo = await setupBranch(client, githubData, context);
|
||||
core.setOutput("BASE_BRANCH", branchInfo.baseBranch);
|
||||
if (branchInfo.claudeBranch) {
|
||||
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
|
||||
}
|
||||
|
||||
// Step 10: Update initial comment with branch link (only if a claude branch was created)
|
||||
// Step 9: Update initial comment with branch link (only if a claude branch was created)
|
||||
if (branchInfo.claudeBranch) {
|
||||
await updateTrackingComment(
|
||||
client,
|
||||
@@ -95,7 +83,7 @@ async function run() {
|
||||
);
|
||||
}
|
||||
|
||||
// Step 11: Create prompt file
|
||||
// Step 10: Create prompt file
|
||||
await createPrompt(
|
||||
commentId,
|
||||
branchInfo.baseBranch,
|
||||
@@ -104,7 +92,7 @@ async function run() {
|
||||
context,
|
||||
);
|
||||
|
||||
// Step 12: Get MCP configuration
|
||||
// Step 11: Get MCP configuration
|
||||
const mcpConfig = await prepareMcpConfig(
|
||||
githubToken,
|
||||
context.repository.owner,
|
||||
|
||||
Reference in New Issue
Block a user