mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 23:14:13 +08:00
Compare commits
2 Commits
v1.0.14
...
add-plugin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23e406ca24 | ||
|
|
f5f27d4716 |
@@ -41,6 +41,10 @@ inputs:
|
||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||
required: false
|
||||
default: ""
|
||||
plugins:
|
||||
description: "Comma-separated list of Claude Code plugins to install (e.g., 'plugin-name1,plugin-name2')"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Auth configuration
|
||||
anthropic_api_key:
|
||||
@@ -177,7 +181,7 @@ runs:
|
||||
# Install Claude Code if no custom executable is provided
|
||||
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
||||
echo "Installing Claude Code..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.25
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
else
|
||||
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
||||
@@ -208,6 +212,7 @@ runs:
|
||||
CLAUDE_CODE_ACTION: "1"
|
||||
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||
INPUT_PLUGINS: ${{ inputs.plugins }}
|
||||
INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }}
|
||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
||||
INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }}
|
||||
|
||||
@@ -18,6 +18,10 @@ inputs:
|
||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||
required: false
|
||||
default: ""
|
||||
plugins:
|
||||
description: "Comma-separated list of Claude Code plugins to install (e.g., 'plugin-name1,plugin-name2')"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Action settings
|
||||
claude_args:
|
||||
@@ -99,7 +103,7 @@ runs:
|
||||
run: |
|
||||
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
||||
echo "Installing Claude Code..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.25
|
||||
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
|
||||
else
|
||||
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
||||
# Add the directory containing the custom executable to PATH
|
||||
@@ -123,6 +127,7 @@ runs:
|
||||
INPUT_PROMPT: ${{ inputs.prompt }}
|
||||
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
|
||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||
INPUT_PLUGINS: ${{ inputs.plugins }}
|
||||
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
|
||||
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { preparePrompt } from "./prepare-prompt";
|
||||
import { runClaude } from "./run-claude";
|
||||
import { setupClaudeCodeSettings } from "./setup-claude-code-settings";
|
||||
import { validateEnvironmentVariables } from "./validate-env";
|
||||
import { installPlugins } from "./install-plugins";
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
@@ -15,6 +16,12 @@ async function run() {
|
||||
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({
|
||||
prompt: process.env.INPUT_PROMPT || "",
|
||||
promptFile: process.env.INPUT_PROMPT_FILE || "",
|
||||
|
||||
80
base-action/src/install-plugins.ts
Normal file
80
base-action/src/install-plugins.ts
Normal 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");
|
||||
}
|
||||
84
base-action/test/install-plugins.test.ts
Normal file
84
base-action/test/install-plugins.test.ts
Normal 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"]);
|
||||
});
|
||||
});
|
||||
@@ -48,12 +48,12 @@ 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\* | - |
|
||||
| `prompt` | Instructions for Claude. Can be a direct prompt or custom template for automation workflows | No | - |
|
||||
| `track_progress` | Force tag mode with tracking comments. Only works with specific PR/issue events. Preserves GitHub context | No | `false` |
|
||||
| `claude_args` | Additional [arguments to pass directly to Claude CLI](https://docs.claude.com/en/docs/claude-code/cli-reference#cli-flags) (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
|
||||
| `claude_args` | Additional arguments to pass directly to Claude CLI (e.g., `--max-turns 10 --model claude-4-0-sonnet-20250805`) | No | "" |
|
||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
||||
| `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 | - |
|
||||
|
||||
@@ -684,7 +684,7 @@ ${
|
||||
- Display the todo list as a checklist in the GitHub comment and mark things off as you go.
|
||||
- REPOSITORY SETUP INSTRUCTIONS: The repository's CLAUDE.md file(s) contain critical repo-specific setup instructions, development guidelines, and preferences. Always read and follow these files, particularly the root CLAUDE.md, as they provide essential context for working with the codebase effectively.
|
||||
- Use h3 headers (###) for section titles in your comments, not h1 headers (#).
|
||||
- Your comment must always include the job run link in the format "[View job run](${GITHUB_SERVER_URL}/${context.repository}/actions/runs/${process.env.GITHUB_RUN_ID})" at the bottom of your response (branch link if there is one should also be included there).
|
||||
- Your comment must always include the job run link (and branch link if there is one) at the bottom.
|
||||
|
||||
CAPABILITIES AND LIMITATIONS:
|
||||
When users ask you to do something, be aware of what you can and cannot do. This section helps you understand how to respond when users request actions outside your scope.
|
||||
|
||||
Reference in New Issue
Block a user