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`
|
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`)
|
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
|
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?
|
### 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?
|
### 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: markwylde/claude-code-gitea-action@v1.0.2
|
- uses: markwylde/claude-code-gitea-action@v1.0.3
|
||||||
with:
|
with:
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
claude_git_name: Claude # optional
|
||||||
|
claude_git_email: claude@anthropic.com # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
| Input | Description | Required | Default |
|
| 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 | - |
|
| `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` |
|
| `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 | - |
|
| `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 | "" |
|
| `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 | - |
|
| `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` |
|
| `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)
|
\*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.
|
> **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
|
## Gitea Configuration
|
||||||
|
|
||||||
This action has been enhanced to work with Gitea installations. The main differences from GitHub are:
|
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
|
# 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)"
|
||||||
@@ -60,6 +63,14 @@ inputs:
|
|||||||
description: "Timeout in minutes for execution"
|
description: "Timeout in minutes for execution"
|
||||||
required: false
|
required: false
|
||||||
default: "30"
|
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:
|
outputs:
|
||||||
execution_file:
|
execution_file:
|
||||||
@@ -96,6 +107,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,11 +136,16 @@ 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 }}
|
||||||
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
|
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)
|
# Provider configuration (for future cloud provider support)
|
||||||
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
|
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
|
||||||
AWS_REGION: ${{ env.AWS_REGION }}
|
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 { 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 +51,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 +63,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 +78,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 +95,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 +104,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,
|
||||||
|
|||||||
@@ -60,15 +60,18 @@ function runGitCommand(command: string): string {
|
|||||||
|
|
||||||
// Helper function to ensure git user is configured
|
// Helper function to ensure git user is configured
|
||||||
function ensureGitUserConfigured(): void {
|
function ensureGitUserConfigured(): void {
|
||||||
|
const gitName = process.env.CLAUDE_GIT_NAME || "Claude";
|
||||||
|
const gitEmail = process.env.CLAUDE_GIT_EMAIL || "claude@anthropic.com";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if user.email is already configured
|
// Check if user.email is already configured
|
||||||
runGitCommand("git config user.email");
|
runGitCommand("git config user.email");
|
||||||
console.log(`[LOCAL-GIT-MCP] Git user.email already configured`);
|
console.log(`[LOCAL-GIT-MCP] Git user.email already configured`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
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 {
|
try {
|
||||||
@@ -77,9 +80,9 @@ function ensureGitUserConfigured(): void {
|
|||||||
console.log(`[LOCAL-GIT-MCP] Git user.name already configured`);
|
console.log(`[LOCAL-GIT-MCP] Git user.name already configured`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
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