mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-02-20 02:22:49 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87eac76ba0 | ||
|
|
96524bd1d8 | ||
|
|
0a1983379e | ||
|
|
90c7a171fc | ||
|
|
07ce5612a4 | ||
|
|
a8a36ced96 |
@@ -65,3 +65,10 @@ When adding new MCP tools:
|
||||
2. **Expose to Claude**: Add the tool name to `BASE_ALLOWED_TOOLS` array in `src/create-prompt/index.ts`
|
||||
3. **Tool Naming**: Follow the pattern `mcp__server_name__tool_name` (e.g., `mcp__local_git_ops__checkout_branch`)
|
||||
4. **Documentation**: Update the prompt's "What You CAN Do" section if the tool adds new capabilities
|
||||
|
||||
## Feature Development Reminders
|
||||
|
||||
When implementing new features that add action inputs, configuration options, or capabilities:
|
||||
1. Always update README.md to document new inputs in the inputs table
|
||||
2. Update example workflows to show how new inputs can be used
|
||||
3. Add appropriate defaults and descriptions to action.yml
|
||||
|
||||
2
FAQ.md
2
FAQ.md
@@ -6,7 +6,7 @@ This FAQ addresses common questions and gotchas when using the Claude Code GitHu
|
||||
|
||||
### Why doesn't tagging @claude from my automated workflow work?
|
||||
|
||||
The `github-actions` user (and other GitHub Apps/bots) cannot trigger subsequent GitHub Actions workflows. This is a GitHub security feature to prevent infinite loops. To make this work, you need to use a Personal Access Token (PAT) instead, which will act as a regular user. When posting a comment on an issue or PR from your workflow, use your PAT instead of the `GITHUB_TOKEN` generated in your workflow.
|
||||
The `github-actions` user cannot trigger subsequent GitHub Actions workflows. This is a GitHub security feature to prevent infinite loops. To make this work, you need to use a Personal Access Token (PAT) instead, which will act as a regular user, or use a separate app token of your own. When posting a comment on an issue or PR from your workflow, use your PAT instead of the `GITHUB_TOKEN` generated in your workflow.
|
||||
|
||||
### Why does Claude say I don't have permission to trigger it?
|
||||
|
||||
|
||||
37
README.md
37
README.md
@@ -60,17 +60,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: markwylde/claude-code-gitea-action@v1.0.2
|
||||
- uses: markwylde/claude-code-gitea-action@v1.0.3
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
claude_git_name: Claude # optional
|
||||
claude_git_email: claude@anthropic.com # optional
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| `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 | - |
|
||||
| `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 | - |
|
||||
@@ -83,11 +86,39 @@ jobs:
|
||||
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
|
||||
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
||||
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
||||
| `claude_git_name` | Git user.name for commits made by Claude | No | `Claude` |
|
||||
| `claude_git_email` | Git user.email for commits made by Claude | No | `claude@anthropic.com` |
|
||||
|
||||
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
|
||||
|
||||
> **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`.
|
||||
|
||||
3. **Configure Workflow**: Set up your workflow to use OAuth authentication:
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: "use-oauth"
|
||||
claude_credentials: ${{ secrets.CLAUDE_CREDENTIALS }}
|
||||
gitea_token: ${{ secrets.GITHUB_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:
|
||||
|
||||
20
action.yml
20
action.yml
@@ -42,7 +42,10 @@ inputs:
|
||||
|
||||
# Auth configuration
|
||||
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
|
||||
gitea_token:
|
||||
description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)"
|
||||
@@ -60,6 +63,14 @@ inputs:
|
||||
description: "Timeout in minutes for execution"
|
||||
required: false
|
||||
default: "30"
|
||||
claude_git_name:
|
||||
description: "Git user.name for commits made by Claude"
|
||||
required: false
|
||||
default: "Claude"
|
||||
claude_git_email:
|
||||
description: "Git user.email for commits made by Claude"
|
||||
required: false
|
||||
default: "claude@anthropic.com"
|
||||
|
||||
outputs:
|
||||
execution_file:
|
||||
@@ -96,6 +107,8 @@ runs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
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: Run Claude Code
|
||||
id: claude-code
|
||||
@@ -123,11 +136,16 @@ runs:
|
||||
USE_BEDROCK: ${{ inputs.use_bedrock }}
|
||||
USE_VERTEX: ${{ inputs.use_vertex }}
|
||||
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
|
||||
CLAUDE_CREDENTIALS: ${{ inputs.claude_credentials }}
|
||||
|
||||
# GitHub token for repository access
|
||||
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
|
||||
|
||||
# Git configuration
|
||||
CLAUDE_GIT_NAME: ${{ inputs.claude_git_name }}
|
||||
CLAUDE_GIT_EMAIL: ${{ inputs.claude_git_email }}
|
||||
|
||||
# Provider configuration (for future cloud provider support)
|
||||
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
|
||||
AWS_REGION: ${{ env.AWS_REGION }}
|
||||
|
||||
63
src/claude/oauth-setup.ts
Normal file
63
src/claude/oauth-setup.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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,29 @@ 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 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 client = createClient(githubToken);
|
||||
|
||||
// Step 2: Parse GitHub context (once for all operations)
|
||||
// Step 3: Parse GitHub context (once for all operations)
|
||||
const context = parseGitHubContext();
|
||||
|
||||
// Step 3: Check write permissions
|
||||
// Step 4: Check write permissions
|
||||
const hasWritePermissions = await checkWritePermissions(
|
||||
client.api,
|
||||
context,
|
||||
@@ -39,7 +51,7 @@ async function run() {
|
||||
);
|
||||
}
|
||||
|
||||
// Step 4: Check trigger conditions
|
||||
// Step 5: Check trigger conditions
|
||||
const containsTrigger = await checkTriggerAction(context);
|
||||
|
||||
// Set outputs that are always needed
|
||||
@@ -51,14 +63,14 @@ async function run() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 5: Check if actor is human
|
||||
// Step 6: Check if actor is human
|
||||
await checkHumanActor(client.api, context);
|
||||
|
||||
// Step 6: Create initial tracking comment
|
||||
// Step 7: Create initial tracking comment
|
||||
const commentId = await createInitialComment(client.api, context);
|
||||
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({
|
||||
client: client,
|
||||
repository: `${context.repository.owner}/${context.repository.repo}`,
|
||||
@@ -66,14 +78,14 @@ async function run() {
|
||||
isPR: context.isPR,
|
||||
});
|
||||
|
||||
// Step 8: Setup branch
|
||||
// Step 9: 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 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) {
|
||||
await updateTrackingComment(
|
||||
client,
|
||||
@@ -83,7 +95,7 @@ async function run() {
|
||||
);
|
||||
}
|
||||
|
||||
// Step 10: Create prompt file
|
||||
// Step 11: Create prompt file
|
||||
await createPrompt(
|
||||
commentId,
|
||||
branchInfo.baseBranch,
|
||||
@@ -92,7 +104,7 @@ async function run() {
|
||||
context,
|
||||
);
|
||||
|
||||
// Step 11: Get MCP configuration
|
||||
// Step 12: Get MCP configuration
|
||||
const mcpConfig = await prepareMcpConfig(
|
||||
githubToken,
|
||||
context.repository.owner,
|
||||
|
||||
@@ -60,15 +60,18 @@ function runGitCommand(command: string): string {
|
||||
|
||||
// Helper function to ensure git user is configured
|
||||
function ensureGitUserConfigured(): void {
|
||||
const gitName = process.env.CLAUDE_GIT_NAME || "Claude";
|
||||
const gitEmail = process.env.CLAUDE_GIT_EMAIL || "claude@anthropic.com";
|
||||
|
||||
try {
|
||||
// Check if user.email is already configured
|
||||
runGitCommand("git config user.email");
|
||||
console.log(`[LOCAL-GIT-MCP] Git user.email already configured`);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[LOCAL-GIT-MCP] Git user.email not configured, setting default`,
|
||||
`[LOCAL-GIT-MCP] Git user.email not configured, setting to: ${gitEmail}`,
|
||||
);
|
||||
runGitCommand('git config user.email "claude@anthropic.com"');
|
||||
runGitCommand(`git config user.email "${gitEmail}"`);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -77,9 +80,9 @@ function ensureGitUserConfigured(): void {
|
||||
console.log(`[LOCAL-GIT-MCP] Git user.name already configured`);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[LOCAL-GIT-MCP] Git user.name not configured, setting default`,
|
||||
`[LOCAL-GIT-MCP] Git user.name not configured, setting to: ${gitName}`,
|
||||
);
|
||||
runGitCommand('git config user.name "Claude"');
|
||||
runGitCommand(`git config user.name "${gitName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user