Compare commits

...

17 Commits

Author SHA1 Message Date
Ashwin Bhat
af32fd318a Revert "feat: add GITHUB_HOST to github-mcp-server for GitHub Enterprise Serv…" (#359)
This reverts commit e07ea013bd.
2025-07-29 11:51:20 -07:00
YutaSaito
e07ea013bd feat: add GITHUB_HOST to github-mcp-server for GitHub Enterprise Server (#343) 2025-07-28 17:39:52 -07:00
aki77
6037d754ac chore: update MCP server image to version 0.9.0 (#344) 2025-07-28 16:20:59 -07:00
GitHub Actions
04b2df22d4 chore: bump Claude Code version to 1.0.62 2025-07-28 22:36:23 +00:00
Ashwin Bhat
8fc9a366cb chore: update Claude Code installation to use bun and version 1.0.61 (#352)
- Switch from npm to bun for Claude Code installation in base-action
- Update Claude Code version from 1.0.59 to 1.0.61 in main action
- Ensures consistent package manager usage across both action files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-28 09:44:51 -07:00
GitHub Actions
7c5a98d59d chore: bump Claude Code version to 1.0.61 2025-07-25 21:06:57 +00:00
km-anthropic
c3e0ab4d6d feat: add agent mode for automation scenarios (#337)
* feat: add agent mode for automation scenarios

- Add agent mode that always triggers without checking for mentions
- Implement Mode interface with support for mode-specific tool configuration
- Add getAllowedTools() and getDisallowedTools() methods to Mode interface
- Simplify tests by combining related test cases
- Update documentation and examples to include agent mode
- Fix TypeScript imports to prevent circular dependencies

Agent mode is designed for automation and workflow_dispatch scenarios
where Claude should always run without requiring trigger phrases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Minor update to readme (from @main to @beta)

* Since workflow_dispatch isn't in the base action, update the examples accordingly

* minor formatting issue

* Update to say beta instead of main

* Fix missed tracking comment to be false

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-24 14:53:15 -07:00
GitHub Actions
94437192fa chore: bump Claude Code version to 1.0.60 2025-07-24 21:02:45 +00:00
Yuku Kotani
9cf75f75b9 feat: format PR and issue body text in prompt variables (#330)
* feat: format PR and issue body text in prompt variables

Apply formatBody function to PR_BODY and ISSUE_BODY variables to properly handle images and markdown formatting in prompt context.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: format PR_BODY and ISSUE_BODY ternary expressions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add claude_code_oauth_token to all GitHub workflow tests

Add claude_code_oauth_token parameter to all test workflow files to support new authentication method. This ensures proper authentication for Claude Code API access in GitHub Actions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Revert "feat: add claude_code_oauth_token to all GitHub workflow tests"

This reverts commit fccc1a0ebd.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-23 22:16:10 -07:00
km-anthropic
a58dc37018 Add mode support (#333)
* Add mode support

* update "as any" with proper "as unknwon as ModeName" casting

* Add documentation to README and registry.ts

* Add  tests for differen event types, integration flows, and error conditions

* Clean up some tests

* Minor test fix

* Minor formatting test + switch from interface to type

* correct the order of mkdir call

* always configureGitAuth as there's already a fallback to handle null users by using the bot ID

* simplify registry setup

---------

Co-authored-by: km-anthropic <km-anthropic@users.noreply.github.com>
2025-07-23 20:35:11 -07:00
Ashwin Bhat
963754fa12 perf: optimize Squid proxy startup time (#334)
* perf: optimize Squid proxy startup time

- Replace fixed 7-second sleep with dynamic readiness check
- Only shutdown existing Squid if actually running
- Add detailed timing logs to track each step's duration
- Expected reduction: ~7-8 seconds to ~1-2 seconds startup overhead

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract squid setup into standalone script

Move squid proxy setup logic from action.yml inline bash script
to scripts/setup-network-restrictions.sh for better maintainability
and cleaner action configuration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Revert "refactor: extract squid setup into standalone script"

This reverts commit b18aa2821d.

* tmp

* Reapply "refactor: extract squid setup into standalone script"

This reverts commit 07f6911549.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-23 20:33:29 -07:00
Ashwin Bhat
3f4d843152 Revert "feat: integrate Claude Code SDK to replace process spawning (#327)" (#335)
* Revert "feat: integrate Claude Code SDK to replace process spawning (#327)"

This reverts commit 204266ca45.

* 1.0.59
2025-07-23 18:42:43 -07:00
GitHub Actions
e26577a930 chore: bump Claude Code version to 1.0.59 2025-07-23 21:38:01 +00:00
GitHub Actions
eba34996fb chore: bump Claude Code version to 1.0.58 2025-07-23 21:24:21 +00:00
Ashwin Bhat
0763498a5a feat: add DETAILED_PERMISSION_MESSAGES env var to Claude Code invocation (#328)
Enables detailed permission messages in Claude Code by setting the
DETAILED_PERMISSION_MESSAGES environment variable to '1' in the
Run Claude Code step.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-22 20:02:46 -07:00
Ashwin Bhat
204266ca45 feat: integrate Claude Code SDK to replace process spawning (#327)
* feat: integrate Claude Code SDK to replace process spawning

- Add @anthropic-ai/claude-code dependency to base-action
- Replace mkfifo/cat process spawning with direct SDK usage
- Remove global Claude Code installation from action.yml files
- Maintain full compatibility with existing options
- Add comprehensive tests for SDK integration

This change makes the implementation cleaner and more reliable by
eliminating the complexity of managing child processes and named pipes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add debugging and bun executable for Claude Code SDK

- Add stderr handler to capture CLI errors
- Explicitly set bun as the executable for the SDK
- This should help diagnose why the CLI is exiting with code 1

* fix: extract mcpServers from parsed MCP config

The SDK expects just the servers object, not the wrapper object with mcpServers property.

* tsc

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-22 16:56:54 -07:00
GitHub Actions
ef304464bb chore: bump Claude Code version to 1.0.58 2025-07-22 23:12:32 +00:00
25 changed files with 812 additions and 127 deletions

View File

@@ -32,7 +32,7 @@ jobs:
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-721fd3e"
"ghcr.io/github/github-mcp-server:sha-efef8ae"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -145,6 +145,8 @@ jobs:
# Or use OAuth token instead:
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
# Optional: set execution mode (default: tag)
# mode: "tag"
# Optional: add custom trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: add assignee trigger for issues
@@ -165,40 +167,79 @@ jobs:
## Inputs
| Input | Description | Required | Default |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `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 | - |
| `override_prompt` | Complete replacement of Claude's prompt with custom template (supports variable substitution) | No | - |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
| `fallback_model` | Enable automatic fallback to specified model when primary model is unavailable | No | - |
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
| `disallowed_tools` | Tools that Claude should never use | No | "" |
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
| `mcp_config` | Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers | No | "" |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML format) | No | "" |
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` |
| Input | Description | Required | Default |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `mode` | Execution mode: 'tag' (default - triggered by mentions/assignments), 'agent' (for automation with no trigger checking) | No | `tag` |
| `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 | - |
| `override_prompt` | Complete replacement of Claude's prompt with custom template (supports variable substitution) | No | - |
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
| `fallback_model` | Enable automatic fallback to specified model when primary model is unavailable | No | - |
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
| `disallowed_tools` | Tools that Claude should never use | No | "" |
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
| `mcp_config` | Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers | No | "" |
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML format) | No | "" |
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` |
\*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.
## Execution Modes
The action supports two execution modes, each optimized for different use cases:
### Tag Mode (Default)
The traditional implementation mode that responds to @claude mentions, issue assignments, or labels.
- **Triggers**: `@claude` mentions, issue assignment, label application
- **Features**: Creates tracking comments with progress checkboxes, full implementation capabilities
- **Use case**: General-purpose code implementation and Q&A
```yaml
- uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# mode: tag is the default
```
### Agent Mode
For automation and scheduled tasks without trigger checking.
- **Triggers**: Always runs (no trigger checking)
- **Features**: Perfect for scheduled tasks, works with `override_prompt`
- **Use case**: Maintenance tasks, automated reporting, scheduled checks
```yaml
- uses: anthropics/claude-code-action@beta
with:
mode: agent
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
override_prompt: |
Check for outdated dependencies and create an issue if any are found.
```
See [`examples/claude-modes.yml`](./examples/claude-modes.yml) for complete examples of each mode.
### Using Custom MCP Configuration
The `mcp_config` input allows you to add custom MCP (Model Context Protocol) servers to extend Claude's capabilities. These servers merge with the built-in GitHub MCP servers.

View File

@@ -24,6 +24,12 @@ inputs:
required: false
default: "claude/"
# Mode configuration
mode:
description: "Execution mode for the action. Valid modes: 'tag' (default - triggered by mentions/assignments), 'agent' (for automation with no trigger checking)"
required: false
default: "tag"
# Claude Code configuration
model:
description: "Model to use (provider-specific format required for Bedrock/Vertex)"
@@ -137,6 +143,7 @@ runs:
run: |
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts
env:
MODE: ${{ inputs.mode }}
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
LABEL_TRIGGER: ${{ inputs.label_trigger }}
@@ -155,50 +162,34 @@ runs:
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
- name: Install Base Action Dependencies
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: |
echo "Installing base-action dependencies..."
cd ${GITHUB_ACTION_PATH}/base-action
bun install
echo "Base-action dependencies installed"
cd -
# Install Claude Code globally
bun install -g @anthropic-ai/claude-code@1.0.62
- name: Setup Network Restrictions
if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != ''
shell: bash
run: |
# Install and configure Squid proxy
sudo apt-get update && sudo apt-get install -y squid
echo "${{ inputs.experimental_allowed_domains }}" > $RUNNER_TEMP/whitelist.txt
# Configure Squid
sudo tee /etc/squid/squid.conf << EOF
http_port 127.0.0.1:3128
acl whitelist dstdomain "$RUNNER_TEMP/whitelist.txt"
acl localhost src 127.0.0.1/32
http_access allow localhost whitelist
http_access deny all
cache deny all
EOF
# Stop any existing squid instance and start with our config
sudo squid -k shutdown || true
sleep 2
sudo rm -f /run/squid.pid
sudo squid -N -d 1 &
sleep 5
# Set proxy environment variables
echo "http_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "https_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "HTTP_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "HTTPS_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
chmod +x ${GITHUB_ACTION_PATH}/scripts/setup-network-restrictions.sh
${GITHUB_ACTION_PATH}/scripts/setup-network-restrictions.sh
env:
EXPERIMENTAL_ALLOWED_DOMAINS: ${{ inputs.experimental_allowed_domains }}
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: |
# Install Claude Code globally
bun install -g @anthropic-ai/claude-code@1.0.57
# Run the base-action
cd ${GITHUB_ACTION_PATH}/base-action
bun install
cd -
bun run ${GITHUB_ACTION_PATH}/base-action/src/index.ts
env:
# Base-action inputs
@@ -219,6 +210,7 @@ runs:
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
NODE_VERSION: ${{ env.NODE_VERSION }}
DETAILED_PERMISSION_MESSAGES: "1"
# Provider configuration
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}

View File

@@ -115,7 +115,7 @@ runs:
- name: Install Claude Code
shell: bash
run: npm install -g @anthropic-ai/claude-code@1.0.57
run: bun install -g @anthropic-ai/claude-code@1.0.62
- name: Run Claude Code Action
shell: bash

View File

@@ -25,13 +25,17 @@
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@types/bun": ["@types/bun@1.2.12", "", { "dependencies": { "bun-types": "1.2.12" } }, "sha512-lY/GQTXDGsolT/TiH72p1tuyUORuRrdV7VwOTOjDOt8uTBJQOJc5zz3ufwwDl0VBaoxotSk4LdP0hhjLJ6ypIQ=="],
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
"@types/node": ["@types/node@20.17.32", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw=="],
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
"bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"prettier": ["prettier@3.5.3", "", { "bin": "bin/prettier.cjs" }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
@@ -39,6 +43,6 @@
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
}
}

View File

@@ -35,17 +35,17 @@
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.11.0", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.16.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg=="],
"@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
"@octokit/core": ["@octokit/core@5.2.1", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ=="],
"@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
"@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
"@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="],
"@octokit/openapi-types": ["@octokit/openapi-types@25.0.0", "", {}, "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw=="],
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
@@ -59,18 +59,20 @@
"@octokit/rest": ["@octokit/rest@21.1.1", "", { "dependencies": { "@octokit/core": "^6.1.4", "@octokit/plugin-paginate-rest": "^11.4.2", "@octokit/plugin-request-log": "^5.3.1", "@octokit/plugin-rest-endpoint-methods": "^13.3.0" } }, "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg=="],
"@octokit/types": ["@octokit/types@14.0.0", "", { "dependencies": { "@octokit/openapi-types": "^25.0.0" } }, "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA=="],
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
"@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="],
"@types/node": ["@types/node@20.17.44", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-50sE4Ibb4BgUMxHrcJQSAU0Fu7fLcTdwcXwRzEF7wnVMWvImFLg2Rxc7SW0vpvaJm4wvhoWEZaQiPpBpocZiUA=="],
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
@@ -101,7 +103,7 @@
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
@@ -127,21 +129,25 @@
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.6", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.1", "", {}, "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA=="],
"eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
"fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
@@ -175,6 +181,8 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -213,6 +221,8 @@
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
@@ -255,12 +265,14 @@
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"universal-user-agent": ["universal-user-agent@7.0.2", "", {}, "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="],
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
@@ -269,9 +281,9 @@
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
"@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
@@ -283,11 +295,11 @@
"@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
"@octokit/graphql/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/graphql/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/plugin-request-log/@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
"@octokit/plugin-request-log/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
@@ -297,7 +309,7 @@
"@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@octokit/rest/@octokit/core": ["@octokit/core@6.1.5", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg=="],
"@octokit/rest/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="],
"@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.6.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw=="],
@@ -323,7 +335,7 @@
"@octokit/plugin-request-log/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/plugin-request-log/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],
@@ -337,7 +349,7 @@
"@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="],
"@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.3", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w=="],
"@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="],
"@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="],

56
examples/claude-modes.yml Normal file
View File

@@ -0,0 +1,56 @@
name: Claude Mode Examples
on:
# Common events for both modes
issue_comment:
types: [created]
issues:
types: [opened, labeled]
pull_request:
types: [opened]
jobs:
# Tag Mode (Default) - Traditional implementation
tag-mode-example:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Tag mode (default) behavior:
# - Scans for @claude mentions in comments, issues, and PRs
# - Only acts when trigger phrase is found
# - Creates tracking comments with progress checkboxes
# - Perfect for: Interactive Q&A, on-demand code changes
# Agent Mode - Automation without triggers
agent-mode-auto-review:
# Automatically review every new PR
if: github.event_name == 'pull_request' && github.event.action == 'opened'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
steps:
- uses: anthropics/claude-code-action@beta
with:
mode: agent
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
override_prompt: |
Review this PR for code quality. Focus on:
- Potential bugs or logic errors
- Security concerns
- Performance issues
Provide specific, actionable feedback.
# Agent mode behavior:
# - NO @claude mention needed - runs immediately
# - Enables true automation (impossible with tag mode)
# - Perfect for: CI/CD integration, automatic reviews, label-based workflows

View File

@@ -36,6 +36,7 @@ jobs:
# Or use OAuth token instead:
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
timeout_minutes: "60"
# mode: tag # Default: responds to @claude mentions
# Optional: Restrict network access to specific domains only
# experimental_allowed_domains: |
# .anthropic.com

View File

@@ -0,0 +1,123 @@
#!/bin/bash
# Setup Network Restrictions with Squid Proxy
# This script sets up a Squid proxy to restrict network access to whitelisted domains only.
set -e
# Check if experimental_allowed_domains is provided
if [ -z "$EXPERIMENTAL_ALLOWED_DOMAINS" ]; then
echo "ERROR: EXPERIMENTAL_ALLOWED_DOMAINS environment variable is required"
exit 1
fi
# Check required environment variables
if [ -z "$RUNNER_TEMP" ]; then
echo "ERROR: RUNNER_TEMP environment variable is required"
exit 1
fi
if [ -z "$GITHUB_ENV" ]; then
echo "ERROR: GITHUB_ENV environment variable is required"
exit 1
fi
echo "Setting up network restrictions with Squid proxy..."
SQUID_START_TIME=$(date +%s.%N)
# Create whitelist file
echo "$EXPERIMENTAL_ALLOWED_DOMAINS" > $RUNNER_TEMP/whitelist.txt
# Ensure each domain has proper format
# If domain doesn't start with a dot and isn't an IP, add the dot for subdomain matching
mv $RUNNER_TEMP/whitelist.txt $RUNNER_TEMP/whitelist.txt.orig
while IFS= read -r domain; do
if [ -n "$domain" ]; then
# Trim whitespace
domain=$(echo "$domain" | xargs)
# If it's not empty and doesn't start with a dot, add one
if [[ "$domain" != .* ]] && [[ ! "$domain" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ".$domain" >> $RUNNER_TEMP/whitelist.txt
else
echo "$domain" >> $RUNNER_TEMP/whitelist.txt
fi
fi
done < $RUNNER_TEMP/whitelist.txt.orig
# Create Squid config with whitelist
echo "http_port 3128" > $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Define ACLs" >> $RUNNER_TEMP/squid.conf
echo "acl whitelist dstdomain \"/etc/squid/whitelist.txt\"" >> $RUNNER_TEMP/squid.conf
echo "acl localnet src 127.0.0.1/32" >> $RUNNER_TEMP/squid.conf
echo "acl localnet src 172.17.0.0/16" >> $RUNNER_TEMP/squid.conf
echo "acl SSL_ports port 443" >> $RUNNER_TEMP/squid.conf
echo "acl Safe_ports port 80" >> $RUNNER_TEMP/squid.conf
echo "acl Safe_ports port 443" >> $RUNNER_TEMP/squid.conf
echo "acl CONNECT method CONNECT" >> $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Deny requests to certain unsafe ports" >> $RUNNER_TEMP/squid.conf
echo "http_access deny !Safe_ports" >> $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Only allow CONNECT to SSL ports" >> $RUNNER_TEMP/squid.conf
echo "http_access deny CONNECT !SSL_ports" >> $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Allow localhost" >> $RUNNER_TEMP/squid.conf
echo "http_access allow localhost" >> $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Allow localnet access to whitelisted domains" >> $RUNNER_TEMP/squid.conf
echo "http_access allow localnet whitelist" >> $RUNNER_TEMP/squid.conf
echo "" >> $RUNNER_TEMP/squid.conf
echo "# Deny everything else" >> $RUNNER_TEMP/squid.conf
echo "http_access deny all" >> $RUNNER_TEMP/squid.conf
echo "Starting Squid proxy..."
# First, remove any existing container
sudo docker rm -f squid-proxy 2>/dev/null || true
# Ensure whitelist file is not empty (Squid fails with empty files)
if [ ! -s "$RUNNER_TEMP/whitelist.txt" ]; then
echo "WARNING: Whitelist file is empty, adding a dummy entry"
echo ".example.com" >> $RUNNER_TEMP/whitelist.txt
fi
# Use sudo to prevent Claude from stopping the container
CONTAINER_ID=$(sudo docker run -d \
--name squid-proxy \
-p 127.0.0.1:3128:3128 \
-v $RUNNER_TEMP/squid.conf:/etc/squid/squid.conf:ro \
-v $RUNNER_TEMP/whitelist.txt:/etc/squid/whitelist.txt:ro \
ubuntu/squid:latest 2>&1) || {
echo "ERROR: Failed to start Squid container"
exit 1
}
# Wait for proxy to be ready (usually < 1 second)
READY=false
for i in {1..30}; do
if nc -z 127.0.0.1 3128 2>/dev/null; then
TOTAL_TIME=$(echo "scale=3; $(date +%s.%N) - $SQUID_START_TIME" | bc)
echo "Squid proxy ready in ${TOTAL_TIME}s"
READY=true
break
fi
sleep 0.1
done
if [ "$READY" != "true" ]; then
echo "ERROR: Squid proxy failed to start within 3 seconds"
echo "Container logs:"
sudo docker logs squid-proxy 2>&1 || true
echo "Container status:"
sudo docker ps -a | grep squid-proxy || true
exit 1
fi
# Set proxy environment variables
echo "http_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "https_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "HTTP_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "HTTPS_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
echo "Network restrictions setup completed successfully"

View File

@@ -20,6 +20,7 @@ import {
import type { ParsedGitHubContext } from "../github/context";
import type { CommonFields, PreparedContext, EventData } from "./types";
import { GITHUB_SERVER_URL } from "../github/api/config";
import type { Mode, ModeContext } from "../modes/types";
export type { CommonFields, PreparedContext } from "./types";
const BASE_ALLOWED_TOOLS = [
@@ -480,8 +481,14 @@ function substitutePromptVariables(
: "",
PR_TITLE: eventData.isPR && contextData?.title ? contextData.title : "",
ISSUE_TITLE: !eventData.isPR && contextData?.title ? contextData.title : "",
PR_BODY: eventData.isPR && contextData?.body ? contextData.body : "",
ISSUE_BODY: !eventData.isPR && contextData?.body ? contextData.body : "",
PR_BODY:
eventData.isPR && contextData?.body
? formatBody(contextData.body, githubData.imageUrlMap)
: "",
ISSUE_BODY:
!eventData.isPR && contextData?.body
? formatBody(contextData.body, githubData.imageUrlMap)
: "",
PR_COMMENTS: eventData.isPR
? formatComments(comments, githubData.imageUrlMap)
: "",
@@ -788,25 +795,30 @@ f. If you are unable to complete certain steps, such as running a linter or test
}
export async function createPrompt(
claudeCommentId: number,
baseBranch: string | undefined,
claudeBranch: string | undefined,
mode: Mode,
modeContext: ModeContext,
githubData: FetchDataResult,
context: ParsedGitHubContext,
) {
try {
// Tag mode requires a comment ID
if (mode.name === "tag" && !modeContext.commentId) {
throw new Error("Tag mode requires a comment ID for prompt generation");
}
// Prepare the context for prompt generation
const preparedContext = prepareContext(
context,
claudeCommentId.toString(),
baseBranch,
claudeBranch,
modeContext.commentId?.toString() || "",
modeContext.baseBranch,
modeContext.claudeBranch,
);
await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, {
recursive: true,
});
// Generate the prompt
// Generate the prompt directly
const promptContent = generatePrompt(
preparedContext,
githubData,
@@ -828,14 +840,29 @@ export async function createPrompt(
const hasActionsReadPermission =
context.inputs.additionalPermissions.get("actions") === "read" &&
context.isPR;
// Get mode-specific tools
const modeAllowedTools = mode.getAllowedTools();
const modeDisallowedTools = mode.getDisallowedTools();
// Combine with existing allowed tools
const combinedAllowedTools = [
...context.inputs.allowedTools,
...modeAllowedTools,
];
const combinedDisallowedTools = [
...context.inputs.disallowedTools,
...modeDisallowedTools,
];
const allAllowedTools = buildAllowedToolsString(
context.inputs.allowedTools,
combinedAllowedTools,
hasActionsReadPermission,
context.inputs.useCommitSigning,
);
const allDisallowedTools = buildDisallowedToolsString(
context.inputs.disallowedTools,
context.inputs.allowedTools,
combinedDisallowedTools,
combinedAllowedTools,
);
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);

View File

@@ -3,21 +3,21 @@
import { readFileSync, existsSync } from "fs";
import { exit } from "process";
export interface ToolUse {
export type ToolUse = {
type: string;
name?: string;
input?: Record<string, any>;
id?: string;
}
};
export interface ToolResult {
export type ToolResult = {
type: string;
tool_use_id?: string;
content?: any;
is_error?: boolean;
}
};
export interface ContentItem {
export type ContentItem = {
type: string;
text?: string;
tool_use_id?: string;
@@ -26,17 +26,17 @@ export interface ContentItem {
name?: string;
input?: Record<string, any>;
id?: string;
}
};
export interface Message {
export type Message = {
content: ContentItem[];
usage?: {
input_tokens?: number;
output_tokens?: number;
};
}
};
export interface Turn {
export type Turn = {
type: string;
subtype?: string;
message?: Message;
@@ -44,16 +44,16 @@ export interface Turn {
cost_usd?: number;
duration_ms?: number;
result?: string;
}
};
export interface GroupedContent {
export type GroupedContent = {
type: string;
tools_count?: number;
data?: Turn;
text_parts?: string[];
tool_calls?: { tool_use: ToolUse; tool_result?: ToolResult }[];
usage?: Record<string, number>;
}
};
export function detectContentType(content: any): string {
const contentStr = String(content).trim();

View File

@@ -7,17 +7,17 @@
import * as core from "@actions/core";
import { setupGitHubToken } from "../github/token";
import { checkTriggerAction } from "../github/validation/trigger";
import { checkHumanActor } from "../github/validation/actor";
import { checkWritePermissions } from "../github/validation/permissions";
import { createInitialComment } from "../github/operations/comments/create-initial";
import { setupBranch } from "../github/operations/branch";
import { configureGitAuth } from "../github/operations/git-config";
import { prepareMcpConfig } from "../mcp/install-mcp-server";
import { createPrompt } from "../create-prompt";
import { createOctokit } from "../github/api/client";
import { fetchGitHubData } from "../github/data/fetcher";
import { parseGitHubContext } from "../github/context";
import { getMode } from "../modes/registry";
import { createPrompt } from "../create-prompt";
async function run() {
try {
@@ -39,8 +39,12 @@ async function run() {
);
}
// Step 4: Check trigger conditions
const containsTrigger = await checkTriggerAction(context);
// Step 4: Get mode and check trigger conditions
const mode = getMode(context.inputs.mode);
const containsTrigger = mode.shouldTrigger(context);
// Set output for action.yml to check
core.setOutput("contains_trigger", containsTrigger.toString());
if (!containsTrigger) {
console.log("No trigger found, skipping remaining steps");
@@ -50,9 +54,16 @@ async function run() {
// Step 5: Check if actor is human
await checkHumanActor(octokit.rest, context);
// Step 6: Create initial tracking comment
const commentData = await createInitialComment(octokit.rest, context);
const commentId = commentData.id;
// Step 6: Create initial tracking comment (mode-aware)
// Some modes (e.g., agent mode) may not need tracking comments
let commentId: number | undefined;
let commentData:
| Awaited<ReturnType<typeof createInitialComment>>
| undefined;
if (mode.shouldCreateTrackingComment()) {
commentData = await createInitialComment(octokit.rest, context);
commentId = commentData.id;
}
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
const githubData = await fetchGitHubData({
@@ -69,7 +80,7 @@ async function run() {
// Step 9: Configure git authentication if not using commit signing
if (!context.inputs.useCommitSigning) {
try {
await configureGitAuth(githubToken, context, commentData.user);
await configureGitAuth(githubToken, context, commentData?.user || null);
} catch (error) {
console.error("Failed to configure git authentication:", error);
throw error;
@@ -77,13 +88,13 @@ async function run() {
}
// Step 10: Create prompt file
await createPrompt(
const modeContext = mode.prepareContext(context, {
commentId,
branchInfo.baseBranch,
branchInfo.claudeBranch,
githubData,
context,
);
baseBranch: branchInfo.baseBranch,
claudeBranch: branchInfo.claudeBranch,
});
await createPrompt(mode, modeContext, githubData, context);
// Step 11: Get MCP configuration
const additionalMcpConfig = process.env.MCP_CONFIG || "";
@@ -94,7 +105,7 @@ async function run() {
branch: branchInfo.claudeBranch || branchInfo.currentBranch,
baseBranch: branchInfo.baseBranch,
additionalMcpConfig,
claudeCommentId: commentId.toString(),
claudeCommentId: commentId?.toString() || "",
allowedTools: context.inputs.allowedTools,
context,
});

View File

@@ -7,6 +7,8 @@ import type {
PullRequestReviewEvent,
PullRequestReviewCommentEvent,
} from "@octokit/webhooks-types";
import type { ModeName } from "../modes/types";
import { DEFAULT_MODE, isValidMode } from "../modes/registry";
export type ParsedGitHubContext = {
runId: string;
@@ -27,6 +29,7 @@ export type ParsedGitHubContext = {
entityNumber: number;
isPR: boolean;
inputs: {
mode: ModeName;
triggerPhrase: string;
assigneeTrigger: string;
labelTrigger: string;
@@ -46,6 +49,11 @@ export type ParsedGitHubContext = {
export function parseGitHubContext(): ParsedGitHubContext {
const context = github.context;
const modeInput = process.env.MODE ?? DEFAULT_MODE;
if (!isValidMode(modeInput)) {
throw new Error(`Invalid mode: ${modeInput}.`);
}
const commonFields = {
runId: process.env.GITHUB_RUN_ID!,
eventName: context.eventName,
@@ -57,6 +65,7 @@ export function parseGitHubContext(): ParsedGitHubContext {
},
actor: context.actor,
inputs: {
mode: modeInput as ModeName,
triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude",
assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "",
labelTrigger: process.env.LABEL_TRIGGER ?? "",

View File

@@ -156,7 +156,7 @@ export async function prepareMcpConfig(
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-721fd3e", // https://github.com/github/github-mcp-server/releases/tag/v0.6.0
"ghcr.io/github/github-mcp-server:sha-efef8ae", // https://github.com/github/github-mcp-server/releases/tag/v0.9.0
],
env: {
GITHUB_PERSONAL_ACCESS_TOKEN: githubToken,

42
src/modes/agent/index.ts Normal file
View File

@@ -0,0 +1,42 @@
import type { Mode } from "../types";
/**
* Agent mode implementation.
*
* This mode is designed for automation and workflow_dispatch scenarios.
* It always triggers (no checking), allows highly flexible configurations,
* and works well with override_prompt for custom workflows.
*
* In the future, this mode could restrict certain tools for safety in automation contexts,
* e.g., disallowing WebSearch or limiting file system operations.
*/
export const agentMode: Mode = {
name: "agent",
description: "Automation mode that always runs without trigger checking",
shouldTrigger() {
return true;
},
prepareContext(context, data) {
return {
mode: "agent",
githubContext: context,
commentId: data?.commentId,
baseBranch: data?.baseBranch,
claudeBranch: data?.claudeBranch,
};
},
getAllowedTools() {
return [];
},
getDisallowedTools() {
return [];
},
shouldCreateTrackingComment() {
return false;
},
};

53
src/modes/registry.ts Normal file
View File

@@ -0,0 +1,53 @@
/**
* Mode Registry for claude-code-action
*
* This module provides access to all available execution modes.
*
* To add a new mode:
* 1. Add the mode name to VALID_MODES below
* 2. Create the mode implementation in a new directory (e.g., src/modes/new-mode/)
* 3. Import and add it to the modes object below
* 4. Update action.yml description to mention the new mode
*/
import type { Mode, ModeName } from "./types";
import { tagMode } from "./tag";
import { agentMode } from "./agent";
export const DEFAULT_MODE = "tag" as const;
export const VALID_MODES = ["tag", "agent"] as const;
/**
* All available modes.
* Add new modes here as they are created.
*/
const modes = {
tag: tagMode,
agent: agentMode,
} as const satisfies Record<ModeName, Mode>;
/**
* Retrieves a mode by name.
* @param name The mode name to retrieve
* @returns The requested mode
* @throws Error if the mode is not found
*/
export function getMode(name: ModeName): Mode {
const mode = modes[name];
if (!mode) {
const validModes = VALID_MODES.join("', '");
throw new Error(
`Invalid mode '${name}'. Valid modes are: '${validModes}'. Please check your workflow configuration.`,
);
}
return mode;
}
/**
* Type guard to check if a string is a valid mode name.
* @param name The string to check
* @returns True if the name is a valid mode name
*/
export function isValidMode(name: string): name is ModeName {
return VALID_MODES.includes(name as ModeName);
}

40
src/modes/tag/index.ts Normal file
View File

@@ -0,0 +1,40 @@
import type { Mode } from "../types";
import { checkContainsTrigger } from "../../github/validation/trigger";
/**
* Tag mode implementation.
*
* The traditional implementation mode that responds to @claude mentions,
* issue assignments, or labels. Creates tracking comments showing progress
* and has full implementation capabilities.
*/
export const tagMode: Mode = {
name: "tag",
description: "Traditional implementation mode triggered by @claude mentions",
shouldTrigger(context) {
return checkContainsTrigger(context);
},
prepareContext(context, data) {
return {
mode: "tag",
githubContext: context,
commentId: data?.commentId,
baseBranch: data?.baseBranch,
claudeBranch: data?.claudeBranch,
};
},
getAllowedTools() {
return [];
},
getDisallowedTools() {
return [];
},
shouldCreateTrackingComment() {
return true;
},
};

56
src/modes/types.ts Normal file
View File

@@ -0,0 +1,56 @@
import type { ParsedGitHubContext } from "../github/context";
export type ModeName = "tag" | "agent";
export type ModeContext = {
mode: ModeName;
githubContext: ParsedGitHubContext;
commentId?: number;
baseBranch?: string;
claudeBranch?: string;
};
export type ModeData = {
commentId?: number;
baseBranch?: string;
claudeBranch?: string;
};
/**
* Mode interface for claude-code-action execution modes.
* Each mode defines its own behavior for trigger detection, prompt generation,
* and tracking comment creation.
*
* Current modes include:
* - 'tag': Traditional implementation triggered by mentions/assignments
* - 'agent': For automation with no trigger checking
*/
export type Mode = {
name: ModeName;
description: string;
/**
* Determines if this mode should trigger based on the GitHub context
*/
shouldTrigger(context: ParsedGitHubContext): boolean;
/**
* Prepares the mode context with any additional data needed for prompt generation
*/
prepareContext(context: ParsedGitHubContext, data?: ModeData): ModeContext;
/**
* Returns the list of tools that should be allowed for this mode
*/
getAllowedTools(): string[];
/**
* Returns the list of tools that should be disallowed for this mode
*/
getDisallowedTools(): string[];
/**
* Determines if this mode should create a tracking comment
*/
shouldCreateTrackingComment(): boolean;
};

View File

@@ -24,6 +24,7 @@ describe("prepareMcpConfig", () => {
entityNumber: 123,
isPR: false,
inputs: {
mode: "tag",
triggerPhrase: "@claude",
assigneeTrigger: "",
labelTrigger: "",

View File

@@ -8,6 +8,7 @@ import type {
} from "@octokit/webhooks-types";
const defaultInputs = {
mode: "tag" as const,
triggerPhrase: "/claude",
assigneeTrigger: "",
labelTrigger: "",

82
test/modes/agent.test.ts Normal file
View File

@@ -0,0 +1,82 @@
import { describe, test, expect, beforeEach } from "bun:test";
import { agentMode } from "../../src/modes/agent";
import type { ParsedGitHubContext } from "../../src/github/context";
import { createMockContext } from "../mockContext";
describe("Agent Mode", () => {
let mockContext: ParsedGitHubContext;
beforeEach(() => {
mockContext = createMockContext({
eventName: "workflow_dispatch",
isPR: false,
});
});
test("agent mode has correct properties and behavior", () => {
// Basic properties
expect(agentMode.name).toBe("agent");
expect(agentMode.description).toBe(
"Automation mode that always runs without trigger checking",
);
expect(agentMode.shouldCreateTrackingComment()).toBe(false);
// Tool methods return empty arrays
expect(agentMode.getAllowedTools()).toEqual([]);
expect(agentMode.getDisallowedTools()).toEqual([]);
// Always triggers regardless of context
const contextWithoutTrigger = createMockContext({
eventName: "workflow_dispatch",
isPR: false,
inputs: {
...createMockContext().inputs,
triggerPhrase: "@claude",
},
payload: {} as any,
});
expect(agentMode.shouldTrigger(contextWithoutTrigger)).toBe(true);
});
test("prepareContext includes all required data", () => {
const data = {
commentId: 789,
baseBranch: "develop",
claudeBranch: "claude/automated-task",
};
const context = agentMode.prepareContext(mockContext, data);
expect(context.mode).toBe("agent");
expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBe(789);
expect(context.baseBranch).toBe("develop");
expect(context.claudeBranch).toBe("claude/automated-task");
});
test("prepareContext works without data", () => {
const context = agentMode.prepareContext(mockContext);
expect(context.mode).toBe("agent");
expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBeUndefined();
expect(context.baseBranch).toBeUndefined();
expect(context.claudeBranch).toBeUndefined();
});
test("agent mode triggers for all event types", () => {
const events = [
"push",
"schedule",
"workflow_dispatch",
"repository_dispatch",
"issue_comment",
"pull_request",
];
events.forEach((eventName) => {
const context = createMockContext({ eventName, isPR: false });
expect(agentMode.shouldTrigger(context)).toBe(true);
});
});
});

View File

@@ -0,0 +1,36 @@
import { describe, test, expect } from "bun:test";
import { getMode, isValidMode } from "../../src/modes/registry";
import type { ModeName } from "../../src/modes/types";
import { tagMode } from "../../src/modes/tag";
import { agentMode } from "../../src/modes/agent";
describe("Mode Registry", () => {
test("getMode returns tag mode by default", () => {
const mode = getMode("tag");
expect(mode).toBe(tagMode);
expect(mode.name).toBe("tag");
});
test("getMode returns agent mode", () => {
const mode = getMode("agent");
expect(mode).toBe(agentMode);
expect(mode.name).toBe("agent");
});
test("getMode throws error for invalid mode", () => {
const invalidMode = "invalid" as unknown as ModeName;
expect(() => getMode(invalidMode)).toThrow(
"Invalid mode 'invalid'. Valid modes are: 'tag', 'agent'. Please check your workflow configuration.",
);
});
test("isValidMode returns true for all valid modes", () => {
expect(isValidMode("tag")).toBe(true);
expect(isValidMode("agent")).toBe(true);
});
test("isValidMode returns false for invalid mode", () => {
expect(isValidMode("invalid")).toBe(false);
expect(isValidMode("review")).toBe(false);
});
});

92
test/modes/tag.test.ts Normal file
View File

@@ -0,0 +1,92 @@
import { describe, test, expect, beforeEach } from "bun:test";
import { tagMode } from "../../src/modes/tag";
import type { ParsedGitHubContext } from "../../src/github/context";
import type { IssueCommentEvent } from "@octokit/webhooks-types";
import { createMockContext } from "../mockContext";
describe("Tag Mode", () => {
let mockContext: ParsedGitHubContext;
beforeEach(() => {
mockContext = createMockContext({
eventName: "issue_comment",
isPR: false,
});
});
test("tag mode has correct properties", () => {
expect(tagMode.name).toBe("tag");
expect(tagMode.description).toBe(
"Traditional implementation mode triggered by @claude mentions",
);
expect(tagMode.shouldCreateTrackingComment()).toBe(true);
});
test("shouldTrigger delegates to checkContainsTrigger", () => {
const contextWithTrigger = createMockContext({
eventName: "issue_comment",
isPR: false,
inputs: {
...createMockContext().inputs,
triggerPhrase: "@claude",
},
payload: {
comment: {
body: "Hey @claude, can you help?",
},
} as IssueCommentEvent,
});
expect(tagMode.shouldTrigger(contextWithTrigger)).toBe(true);
const contextWithoutTrigger = createMockContext({
eventName: "issue_comment",
isPR: false,
inputs: {
...createMockContext().inputs,
triggerPhrase: "@claude",
},
payload: {
comment: {
body: "This is just a regular comment",
},
} as IssueCommentEvent,
});
expect(tagMode.shouldTrigger(contextWithoutTrigger)).toBe(false);
});
test("prepareContext includes all required data", () => {
const data = {
commentId: 123,
baseBranch: "main",
claudeBranch: "claude/fix-bug",
};
const context = tagMode.prepareContext(mockContext, data);
expect(context.mode).toBe("tag");
expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBe(123);
expect(context.baseBranch).toBe("main");
expect(context.claudeBranch).toBe("claude/fix-bug");
});
test("prepareContext works without data", () => {
const context = tagMode.prepareContext(mockContext);
expect(context.mode).toBe("tag");
expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBeUndefined();
expect(context.baseBranch).toBeUndefined();
expect(context.claudeBranch).toBeUndefined();
});
test("getAllowedTools returns empty array", () => {
expect(tagMode.getAllowedTools()).toEqual([]);
});
test("getDisallowedTools returns empty array", () => {
expect(tagMode.getDisallowedTools()).toEqual([]);
});
});

View File

@@ -60,6 +60,7 @@ describe("checkWritePermissions", () => {
entityNumber: 1,
isPR: false,
inputs: {
mode: "tag",
triggerPhrase: "@claude",
assigneeTrigger: "",
labelTrigger: "",

View File

@@ -28,6 +28,7 @@ describe("checkContainsTrigger", () => {
eventName: "issues",
eventAction: "opened",
inputs: {
mode: "tag",
triggerPhrase: "/claude",
assigneeTrigger: "",
labelTrigger: "",
@@ -60,6 +61,7 @@ describe("checkContainsTrigger", () => {
},
} as IssuesEvent,
inputs: {
mode: "tag",
triggerPhrase: "/claude",
assigneeTrigger: "",
labelTrigger: "",
@@ -276,6 +278,7 @@ describe("checkContainsTrigger", () => {
},
} as PullRequestEvent,
inputs: {
mode: "tag",
triggerPhrase: "@claude",
assigneeTrigger: "",
labelTrigger: "",
@@ -309,6 +312,7 @@ describe("checkContainsTrigger", () => {
},
} as PullRequestEvent,
inputs: {
mode: "tag",
triggerPhrase: "@claude",
assigneeTrigger: "",
labelTrigger: "",
@@ -342,6 +346,7 @@ describe("checkContainsTrigger", () => {
},
} as PullRequestEvent,
inputs: {
mode: "tag",
triggerPhrase: "@claude",
assigneeTrigger: "",
labelTrigger: "",