Compare commits

..

14 Commits

Author SHA1 Message Date
Wanghong Yuan MacBook
23e406ca24 Add plugins input to base-action/action.yml
Adds plugin installation support to the standalone base-action.

Changes:
- Added `plugins` input definition (comma-separated list)
- Added INPUT_PLUGINS environment variable to pass to src/index.ts

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 23:07:28 -07:00
Wanghong Yuan MacBook
f5f27d4716 Add plugins input to GitHub Action
This commit adds support for installing Claude Code plugins via a new `plugins` input parameter.

Changes:
- Added `plugins` input to action.yml (comma-separated list)
- Created `install-plugins.ts` with plugin installation logic
- Added comprehensive tests in `install-plugins.test.ts`
- Updated base-action index.ts to call plugin installation
- Plugins are installed after settings setup but before Claude execution

Usage example:
```yaml
- uses: anthropic-ai/claude-code-action@main
  with:
    plugins: "feature-dev,test-coverage-reviewer"
```

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 22:32:57 -07:00
GitHub Actions
fd20c95358 chore: bump Claude Code version to 2.0.24 2025-10-20 19:12:24 +00:00
GitHub Actions
d808160c26 chore: bump Claude Code version to 2.0.23 2025-10-20 17:58:41 +00:00
Okumura Takahiro
3eacedbeb7 Update github-mcp-server to v0.17.1 (#613) 2025-10-20 09:14:27 -07:00
Dale Seo
f52f12eba5 chore: upgrade actions/checkout from v4 to v5 (#632) 2025-10-20 09:11:13 -07:00
GitHub Actions
4a85933f25 chore: bump Claude Code version to 2.0.22 2025-10-17 22:29:52 +00:00
GitHub Actions
ba6edd55ef chore: bump Claude Code version to 2.0.21 2025-10-17 00:48:29 +00:00
GitHub Actions
06461dddff chore: bump Claude Code version to 2.0.20 2025-10-16 16:51:26 +00:00
GitHub Actions
c2a94eead0 chore: bump Claude Code version to 2.0.19 2025-10-15 22:29:38 +00:00
Ashwin Bhat
1c0c3eaced docs: document GitHub App permissions in security guide (#607)
Clarifies which permissions are currently used (Contents, Pull Requests, Issues) versus those requested for planned future features (Discussions, Actions, Checks, Workflows).

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-15 10:12:11 -07:00
GitHub Actions
23d2d6c6b4 chore: bump Claude Code version to 2.0.17 2025-10-15 16:58:01 +00:00
GitHub Actions
e8bad57227 chore: bump Claude Code version to 2.0.15 2025-10-14 17:50:14 +00:00
GitHub Actions
0a6d62601b chore: bump Claude Code version to 2.0.14 2025-10-10 21:25:16 +00:00
24 changed files with 227 additions and 35 deletions

View File

@@ -9,7 +9,7 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
@@ -24,7 +24,7 @@ jobs:
prettier: prettier:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v1 - uses: oven-sh/setup-bun@v1
with: with:
@@ -39,7 +39,7 @@ jobs:
typecheck: typecheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:

View File

@@ -13,7 +13,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -25,7 +25,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -19,7 +19,7 @@ jobs:
next_version: ${{ steps.next_version.outputs.next_version }} next_version: ${{ steps.next_version.outputs.next_version }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -91,7 +91,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -116,7 +116,7 @@ jobs:
environment: production environment: production
steps: steps:
- name: Checkout base-action repo - name: Checkout base-action repo
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
repository: anthropics/claude-code-base-action repository: anthropics/claude-code-base-action
token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }} token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}

View File

@@ -41,6 +41,10 @@ inputs:
description: "Claude Code settings as JSON string or path to settings JSON file" description: "Claude Code settings as JSON string or path to settings JSON file"
required: false required: false
default: "" default: ""
plugins:
description: "Comma-separated list of Claude Code plugins to install (e.g., 'plugin-name1,plugin-name2')"
required: false
default: ""
# Auth configuration # Auth configuration
anthropic_api_key: anthropic_api_key:
@@ -177,7 +181,7 @@ runs:
# Install Claude Code if no custom executable is provided # Install Claude Code if no custom executable is provided
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
echo "Installing Claude Code..." echo "Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.13 curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
else else
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
@@ -208,6 +212,7 @@ runs:
CLAUDE_CODE_ACTION: "1" CLAUDE_CODE_ACTION: "1"
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
INPUT_SETTINGS: ${{ inputs.settings }} INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_PLUGINS: ${{ inputs.plugins }}
INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }} INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }}
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }} INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }}

View File

@@ -336,7 +336,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -18,6 +18,10 @@ inputs:
description: "Claude Code settings as JSON string or path to settings JSON file" description: "Claude Code settings as JSON string or path to settings JSON file"
required: false required: false
default: "" default: ""
plugins:
description: "Comma-separated list of Claude Code plugins to install (e.g., 'plugin-name1,plugin-name2')"
required: false
default: ""
# Action settings # Action settings
claude_args: claude_args:
@@ -99,7 +103,7 @@ runs:
run: | run: |
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
echo "Installing Claude Code..." echo "Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.13 curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
else else
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
# Add the directory containing the custom executable to PATH # Add the directory containing the custom executable to PATH
@@ -123,6 +127,7 @@ runs:
INPUT_PROMPT: ${{ inputs.prompt }} INPUT_PROMPT: ${{ inputs.prompt }}
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }} INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
INPUT_SETTINGS: ${{ inputs.settings }} INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_PLUGINS: ${{ inputs.plugins }}
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }} INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }} INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }} INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}

View File

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

View File

@@ -5,6 +5,7 @@ import { preparePrompt } from "./prepare-prompt";
import { runClaude } from "./run-claude"; import { runClaude } from "./run-claude";
import { setupClaudeCodeSettings } from "./setup-claude-code-settings"; import { setupClaudeCodeSettings } from "./setup-claude-code-settings";
import { validateEnvironmentVariables } from "./validate-env"; import { validateEnvironmentVariables } from "./validate-env";
import { installPlugins } from "./install-plugins";
async function run() { async function run() {
try { try {
@@ -15,6 +16,12 @@ async function run() {
undefined, // homeDir undefined, // homeDir
); );
// Install plugins if specified
await installPlugins(
process.env.INPUT_PLUGINS,
process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE || "claude",
);
const promptConfig = await preparePrompt({ const promptConfig = await preparePrompt({
prompt: process.env.INPUT_PROMPT || "", prompt: process.env.INPUT_PROMPT || "",
promptFile: process.env.INPUT_PROMPT_FILE || "", promptFile: process.env.INPUT_PROMPT_FILE || "",

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bun
import { spawn } from "child_process";
// Declare console as global for TypeScript
declare const console: {
log: (message: string) => void;
error: (message: string) => void;
};
/**
* Parses a comma-separated list of plugin names and returns an array of trimmed plugin names
*/
export function parsePlugins(pluginsInput: string | undefined): string[] {
if (!pluginsInput || pluginsInput.trim() === "") {
return [];
}
return pluginsInput
.split(",")
.map((plugin) => plugin.trim())
.filter((plugin) => plugin.length > 0);
}
/**
* Installs a single Claude Code plugin
*/
export async function installPlugin(
pluginName: string,
claudeExecutable: string = "claude",
): Promise<void> {
return new Promise((resolve, reject) => {
const process = spawn(claudeExecutable, ["plugin", "install", pluginName], {
stdio: "inherit",
});
process.on("close", (code: number | null) => {
if (code === 0) {
resolve();
} else {
reject(
new Error(
`Failed to install plugin '${pluginName}' (exit code: ${code})`,
),
);
}
});
process.on("error", (err: Error) => {
reject(
new Error(`Failed to install plugin '${pluginName}': ${err.message}`),
);
});
});
}
/**
* Installs Claude Code plugins from a comma-separated list
*/
export async function installPlugins(
pluginsInput: string | undefined,
claudeExecutable: string = "claude",
): Promise<void> {
const plugins = parsePlugins(pluginsInput);
if (plugins.length === 0) {
console.log("No plugins to install");
return;
}
console.log(`Installing ${plugins.length} plugin(s)...`);
for (const plugin of plugins) {
console.log(`Installing plugin: ${plugin}`);
await installPlugin(plugin, claudeExecutable);
console.log(`✓ Successfully installed: ${plugin}`);
}
console.log("All plugins installed successfully");
}

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import { parsePlugins } from "../src/install-plugins";
describe("parsePlugins", () => {
test("should return empty array for undefined input", () => {
expect(parsePlugins(undefined)).toEqual([]);
});
test("should return empty array for empty string", () => {
expect(parsePlugins("")).toEqual([]);
});
test("should return empty array for whitespace-only string", () => {
expect(parsePlugins(" \n\t ")).toEqual([]);
});
test("should parse single plugin", () => {
expect(parsePlugins("feature-dev")).toEqual(["feature-dev"]);
});
test("should parse multiple plugins", () => {
expect(parsePlugins("feature-dev,test-coverage-reviewer")).toEqual([
"feature-dev",
"test-coverage-reviewer",
]);
});
test("should trim whitespace around plugin names", () => {
expect(parsePlugins(" feature-dev , test-coverage-reviewer ")).toEqual([
"feature-dev",
"test-coverage-reviewer",
]);
});
test("should handle spaces between commas", () => {
expect(
parsePlugins(
"feature-dev, test-coverage-reviewer, code-quality-reviewer",
),
).toEqual([
"feature-dev",
"test-coverage-reviewer",
"code-quality-reviewer",
]);
});
test("should filter out empty values from consecutive commas", () => {
expect(parsePlugins("feature-dev,,test-coverage-reviewer")).toEqual([
"feature-dev",
"test-coverage-reviewer",
]);
});
test("should handle trailing comma", () => {
expect(parsePlugins("feature-dev,test-coverage-reviewer,")).toEqual([
"feature-dev",
"test-coverage-reviewer",
]);
});
test("should handle leading comma", () => {
expect(parsePlugins(",feature-dev,test-coverage-reviewer")).toEqual([
"feature-dev",
"test-coverage-reviewer",
]);
});
test("should handle plugins with special characters", () => {
expect(parsePlugins("@scope/plugin-name,plugin-name-2")).toEqual([
"@scope/plugin-name",
"plugin-name-2",
]);
});
test("should handle complex whitespace patterns", () => {
expect(
parsePlugins(
"\n feature-dev \n,\t test-coverage-reviewer\t, code-quality \n",
),
).toEqual(["feature-dev", "test-coverage-reviewer", "code-quality"]);
});
});

View File

@@ -127,7 +127,7 @@ For performance, Claude uses shallow clones:
If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step. If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step.
``` ```
- uses: actions/checkout@v4 - uses: actions/checkout@v5
depth: 0 # will fetch full repo history depth: 0 # will fetch full repo history
``` ```

View File

@@ -19,11 +19,22 @@
## GitHub App Permissions ## GitHub App Permissions
The [Claude Code GitHub app](https://github.com/apps/claude) requires these permissions: The [Claude Code GitHub app](https://github.com/apps/claude) requests the following permissions:
- **Pull Requests**: Read and write to create PRs and push changes ### Currently Used Permissions
- **Issues**: Read and write to respond to issues
- **Contents**: Read and write to modify repository files - **Contents** (Read & Write): For reading repository files and creating branches
- **Pull Requests** (Read & Write): For reading PR data and creating/updating pull requests
- **Issues** (Read & Write): For reading issue data and updating issue comments
### Permissions for Future Features
The following permissions are requested but not yet actively used. These will enable planned features in future releases:
- **Discussions** (Read & Write): For interaction with GitHub Discussions
- **Actions** (Read): For accessing workflow run data and logs
- **Checks** (Read): For reading check run results
- **Workflows** (Read & Write): For triggering and managing GitHub Actions workflows
## Commit Signing ## Commit Signing

View File

@@ -35,7 +35,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -89,7 +89,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -153,7 +153,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -211,7 +211,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -268,7 +268,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -344,7 +344,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -456,7 +456,7 @@ jobs:
pull-requests: write pull-requests: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
ref: ${{ github.event.pull_request.head.ref }} ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0 fetch-depth: 0
@@ -513,7 +513,7 @@ jobs:
security-events: write security-events: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
ref: ${{ github.event.workflow_run.head_branch }} ref: ${{ github.event.workflow_run.head_branch }}
fetch-depth: 0 fetch-depth: 0

View File

@@ -26,7 +26,7 @@ jobs:
actions: read # Required for Claude to read CI results on PRs actions: read # Required for Claude to read CI results on PRs
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 2 # Need at least 2 commits to analyze the latest fetch-depth: 2 # Need at least 2 commits to analyze the latest

View File

@@ -16,7 +16,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -18,7 +18,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 1 fetch-depth: 1

View File

@@ -209,7 +209,7 @@ export async function prepareMcpConfig(
"GITHUB_PERSONAL_ACCESS_TOKEN", "GITHUB_PERSONAL_ACCESS_TOKEN",
"-e", "-e",
"GITHUB_HOST", "GITHUB_HOST",
"ghcr.io/github/github-mcp-server:sha-efef8ae", // https://github.com/github/github-mcp-server/releases/tag/v0.9.0 "ghcr.io/github/github-mcp-server:sha-23fa0dd", // https://github.com/github/github-mcp-server/releases/tag/v0.17.1
], ],
env: { env: {
GITHUB_PERSONAL_ACCESS_TOKEN: githubToken, GITHUB_PERSONAL_ACCESS_TOKEN: githubToken,