From 52882e1d74bca9d93f8ba1048ca242003579f06b Mon Sep 17 00:00:00 2001 From: Ryan Alavi Date: Tue, 2 Jun 2026 22:22:51 +1000 Subject: [PATCH] feat: add claude_args passthrough and structured_output (closes #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring the upstream v1 `claude_args` input and `structured_output` output into this Gitea fork so downstream workflows can run the agent with arbitrary Claude CLI flags (notably `--json-schema`) and read the schema-validated verdict back as an action output, without an agent-written file. Approach (issue Option B, ported rather than bumped): the production "Run Claude Code" step previously delegated to the external anthropics/claude-code-base-action@v0.0.63, which has neither input. Upstream base-action main has them but has migrated to the Claude Agent SDK — a wholesale divergence that would change v0.0.63 behavior every downstream step relies on. Instead this invokes the vendored base-action directly via `bun run ${github.action_path}/base-action/src/index.ts` (the same github.action_path pattern prepare.ts already uses, reliable on Gitea), and ports only the two features onto the v0.0.63 process-spawn engine. - base-action/src/run-claude.ts: tokenize claude_args with shell-quote (comment-line stripping; quoted/file-path schemas survive intact) and append after the byte-identical BASE_ARGS, so empty claude_args yields an unchanged arg list; extract structured_output from the stream-json result event when --json-schema is present, failing loudly if absent. - base-action/src/index.ts: forward INPUT_CLAUDE_ARGS. - base-action/action.yml: add claude_args input + structured_output output. - action.yml: add claude_args input + structured_output output; replace the external base-action delegation with a direct bun-run of the vendored copy (full INPUT_*/provider env contract derived from the vendored validate-env/index); bump the default Claude CLI install to 2.1.160 (--json-schema requires a newer CLI than 1.0.117). - shell-quote added to both package.json files; root bun.lock reconciled (it was stale vs package.json — non-frozen installs already resolved the newer tree). - Tests for tokenizer, hasJsonSchema, and structured-output extraction. - README: inputs/outputs tables, "Structured output" + "Version pins". - examples/gitea-structured-output.yml. Follow-up (cannot be done from this repo): consumers using a pre-baked runner image (path_to_claude_code_executable) must rebuild that image with a --json-schema-capable Claude CLI and restart the runner. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 47 ++++++++++ action.yml | 80 ++++++++-------- base-action/action.yml | 8 ++ base-action/bun.lock | 7 ++ base-action/package.json | 4 +- base-action/src/index.ts | 1 + base-action/src/run-claude.ts | 103 ++++++++++++++++++++- base-action/test/run-claude.test.ts | 130 +++++++++++++++++++++++++- bun.lock | 133 ++++++++++++++++----------- examples/gitea-structured-output.yml | 64 +++++++++++++ package.json | 2 + 11 files changed, 482 insertions(+), 97 deletions(-) create mode 100644 examples/gitea-structured-output.yml diff --git a/README.md b/README.md index 0e7c32f..cfe0e51 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ jobs: | `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 | "" | +| `claude_args` | Additional arguments forwarded verbatim to the Claude CLI (e.g. `--json-schema /path/to/schema.json`). See [Structured output](#structured-output). Requires a Claude CLI new enough to support the flags used. | No | "" | | `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" | | `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue and PR assignment | No | - | | `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` | @@ -77,6 +78,52 @@ jobs: > **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration. +## Outputs + +| Output | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `execution_file` | Path to the JSON file containing the Claude Code execution log | +| `structured_output` | JSON string of the schema-validated result, set only when `--json-schema` is passed via `claude_args`. Parse with `fromJSON()` or `jq`. | +| `branch_name` | The branch created by Claude Code for this execution (issue flows) | + +## Structured output + +`claude_args` forwards arbitrary flags straight to the Claude CLI. The primary use case is asking the model for a **schema-validated result** instead of having the agent write it to a file. When you pass `--json-schema`, the action surfaces the validated object as the `structured_output` action output (no agent-written file required). + +```yaml +- uses: ryan/claude-code-gitea-action@gitea + id: review + with: + gitea_token: ${{ secrets.GITHUB_TOKEN }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + direct_prompt: "Review this PR and return a verdict that matches the schema." + # Prefer the file-path form — it has no spaces or `$` for the shell + # tokenizer to mangle, so a schema containing $ref/$defs round-trips safely. + claude_args: --json-schema ${{ github.workspace }}/.gitea/review-schema.json + +- name: Use the verdict + shell: bash + run: | + echo '${{ steps.review.outputs.structured_output }}' | jq -r '.decision' +``` + +Notes: + +- **Use the file-path form** (`--json-schema /path/to/schema.json`). An inline schema also works if single-quoted (`--json-schema '{"type":"object"}'`), but the schema must be single-quoted so the shell tokenizer treats it as one literal token and does not expand `$` (e.g. `$ref`). +- If `--json-schema` is supplied but the model returns no structured result, the run **fails loudly** rather than emitting an empty output. +- **Claude CLI version.** `--json-schema` requires a recent CLI. This action installs `2.1.160` by default. If you pin `path_to_claude_code_executable` (e.g. a pre-baked runner image), that baked CLI must be `--json-schema`-capable — bump it independently. See [Version pins](#version-pins). + +### Version pins + +This fork runs Claude via the vendored `base-action/` (invoked directly, not the external `anthropics/claude-code-base-action`). The moving pieces: + +| Component | Where it's pinned | Current value | +| -------------------------------------- | -------------------------------------------------- | ------------- | +| Claude CLI (default install) | `action.yml` → "Install Claude" step | `2.1.160` | +| Claude CLI (pre-baked runner image) | the runner image's Dockerfile (`CLAUDE_CLI_VERSION`) | set by you | + +If you consume this action through a pre-baked runner image that sets `path_to_claude_code_executable`, the action's own install step is skipped — so enabling `--json-schema` there requires **rebuilding that runner image** with a `--json-schema`-capable CLI and restarting the runner. That is outside this repository. + ## Gitea Configuration This action has been enhanced to work with Gitea installations. The main differences from GitHub are: diff --git a/action.yml b/action.yml index 45892c8..8f573c7 100644 --- a/action.yml +++ b/action.yml @@ -81,6 +81,10 @@ inputs: description: "Enable automatic fallback to specified model when default model is overloaded" required: false default: "" + claude_args: + description: "Additional arguments to pass directly to the Claude CLI, forwarded verbatim (e.g. '--json-schema /path/to/schema.json' to get schema-validated output via the structured_output output). Requires a Claude CLI new enough to support the flags used (see README)." + required: false + default: "" # Auth configuration anthropic_api_key: @@ -131,6 +135,9 @@ outputs: execution_file: description: "Path to the Claude Code execution output file" value: ${{ steps.claude-code.outputs.execution_file }} + structured_output: + description: "JSON string of the schema-validated result when --json-schema is provided in claude_args (parse with fromJSON() or jq)" + value: ${{ steps.claude-code.outputs.structured_output }} branch_name: description: "The branch created by Claude Code for this execution" value: ${{ steps.prepare.outputs.CLAUDE_BRANCH }} @@ -177,10 +184,13 @@ runs: if: steps.prepare.outputs.contains_trigger == 'true' shell: bash run: | - # Install Claude Code if no custom executable is provided + # Install Claude Code if no custom executable is provided. + # Version must be new enough to support any flags passed via claude_args + # (e.g. --json-schema). Runner images that bake their own CLI via + # path_to_claude_code_executable must bump their pin independently. if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then echo "Installing Claude Code..." - curl -fsSL https://claude.ai/install.sh | bash -s 1.0.117 + curl -fsSL https://claude.ai/install.sh | bash -s 2.1.160 echo "$HOME/.local/bin" >> "$GITHUB_PATH" else echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" @@ -189,50 +199,40 @@ runs: echo "$CLAUDE_DIR" >> "$GITHUB_PATH" fi # TODO pass claude_code_executable as input and use it here - - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@v0.0.63 - with: - prompt_file: /tmp/claude-prompts/claude-prompt.txt - allowed_tools: ${{ env.ALLOWED_TOOLS }} - disallowed_tools: ${{ env.DISALLOWED_TOOLS }} - max_turns: ${{ inputs.max_turns }} - timeout_minutes: ${{ inputs.timeout_minutes }} - model: ${{ inputs.model || inputs.anthropic_model }} - mcp_config: ${{ steps.prepare.outputs.mcp_config }} - use_bedrock: ${{ inputs.use_bedrock }} - use_vertex: ${{ inputs.use_vertex }} - anthropic_api_key: ${{ inputs.anthropic_api_key }} - claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }} - settings: ${{ inputs.settings }} - system_prompt: ${{ inputs.system_prompt }} - append_system_prompt: ${{ inputs.append_system_prompt }} - claude_env: ${{ inputs.claude_env }} - fallback_model: ${{ inputs.fallback_model }} - use_node_cache: ${{ inputs.use_node_cache }} + shell: bash + run: | + bun run ${{ github.action_path }}/base-action/src/index.ts env: - # Core configuration - PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt - ALLOWED_TOOLS: ${{ env.ALLOWED_TOOLS }} - DISALLOWED_TOOLS: ${{ env.DISALLOWED_TOOLS }} - TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} - MODEL: ${{ inputs.model || inputs.anthropic_model }} + # Core configuration. The vendored base-action reads these INPUT_* env + # vars directly (see base-action/src/index.ts), so we map them here + # instead of delegating to the external claude-code-base-action. This + # lets us forward claude_args and surface structured_output while + # keeping the Gitea adaptations this fork relies on. + CLAUDE_CODE_ACTION: "1" + INPUT_PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt + INPUT_ALLOWED_TOOLS: ${{ env.ALLOWED_TOOLS }} + INPUT_DISALLOWED_TOOLS: ${{ env.DISALLOWED_TOOLS }} + INPUT_MAX_TURNS: ${{ inputs.max_turns }} + INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} + INPUT_MCP_CONFIG: ${{ steps.prepare.outputs.mcp_config }} + INPUT_SETTINGS: ${{ inputs.settings }} + INPUT_SYSTEM_PROMPT: ${{ inputs.system_prompt }} + INPUT_APPEND_SYSTEM_PROMPT: ${{ inputs.append_system_prompt }} + INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} + INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} + INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }} ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }} - MCP_CONFIG: ${{ steps.prepare.outputs.mcp_config }} - USE_BEDROCK: ${{ inputs.use_bedrock }} - USE_VERTEX: ${{ inputs.use_vertex }} + + # Provider selection (base-action validate-env reads the '1'/'' form). + CLAUDE_CODE_USE_BEDROCK: ${{ inputs.use_bedrock == 'true' && '1' || '' }} + CLAUDE_CODE_USE_VERTEX: ${{ inputs.use_vertex == 'true' && '1' || '' }} + + # Auth configuration ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }} - - # New settings support - SETTINGS: ${{ inputs.settings }} - SYSTEM_PROMPT: ${{ inputs.system_prompt }} - APPEND_SYSTEM_PROMPT: ${{ inputs.append_system_prompt }} - CLAUDE_ENV: ${{ inputs.claude_env }} - FALLBACK_MODEL: ${{ inputs.fallback_model }} - USE_NODE_CACHE: ${{ inputs.use_node_cache }} # GitHub token for repository access GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} @@ -242,7 +242,7 @@ runs: CLAUDE_GIT_NAME: ${{ inputs.claude_git_name }} CLAUDE_GIT_EMAIL: ${{ inputs.claude_git_email }} - # Provider configuration (for future cloud provider support) + # Provider configuration (for cloud provider support) ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }} AWS_REGION: ${{ env.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }} diff --git a/base-action/action.yml b/base-action/action.yml index 02c9d3b..f161c03 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -55,6 +55,10 @@ inputs: description: "Custom environment variables to pass to Claude Code execution (YAML multiline format)" required: false default: "" + claude_args: + description: "Additional arguments to pass directly to the Claude CLI (e.g. '--json-schema /path/to/schema.json'). Forwarded verbatim after the action-managed arguments." + required: false + default: "" # Action settings timeout_minutes: @@ -92,6 +96,9 @@ outputs: execution_file: description: "Path to the JSON file containing Claude Code execution log" value: ${{ steps.run_claude.outputs.execution_file }} + structured_output: + description: "JSON string of the schema-validated result when --json-schema is provided in claude_args (parse with fromJSON() or jq)" + value: ${{ steps.run_claude.outputs.structured_output }} runs: using: "composite" @@ -143,6 +150,7 @@ runs: INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} + INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }} # Provider configuration ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} diff --git a/base-action/bun.lock b/base-action/bun.lock index 0f2bb60..a91c882 100644 --- a/base-action/bun.lock +++ b/base-action/bun.lock @@ -1,14 +1,17 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@anthropic-ai/claude-code-base-action", "dependencies": { "@actions/core": "^1.10.1", + "shell-quote": "^1.8.1", }, "devDependencies": { "@types/bun": "^1.2.12", "@types/node": "^20.0.0", + "@types/shell-quote": "^1.7.5", "prettier": "3.5.3", "typescript": "^5.8.3", }, @@ -31,12 +34,16 @@ "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + "@types/shell-quote": ["@types/shell-quote@1.7.5", "", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="], + "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=="], + "shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="], + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], diff --git a/base-action/package.json b/base-action/package.json index eb9165e..b856bc5 100644 --- a/base-action/package.json +++ b/base-action/package.json @@ -10,11 +10,13 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@actions/core": "^1.10.1" + "@actions/core": "^1.10.1", + "shell-quote": "^1.8.1" }, "devDependencies": { "@types/bun": "^1.2.12", "@types/node": "^20.0.0", + "@types/shell-quote": "^1.7.5", "prettier": "3.5.3", "typescript": "^5.8.3" } diff --git a/base-action/src/index.ts b/base-action/src/index.ts index ac6fc6f..2992191 100644 --- a/base-action/src/index.ts +++ b/base-action/src/index.ts @@ -27,6 +27,7 @@ async function run() { claudeEnv: process.env.INPUT_CLAUDE_ENV, fallbackModel: process.env.INPUT_FALLBACK_MODEL, model: process.env.ANTHROPIC_MODEL, + claudeArgs: process.env.INPUT_CLAUDE_ARGS, }); } catch (error) { core.setFailed(`Action failed with error: ${error}`); diff --git a/base-action/src/run-claude.ts b/base-action/src/run-claude.ts index 70e38d7..3011a5e 100644 --- a/base-action/src/run-claude.ts +++ b/base-action/src/run-claude.ts @@ -4,6 +4,7 @@ import { promisify } from "util"; import { unlink, writeFile, stat } from "fs/promises"; import { createWriteStream } from "fs"; import { spawn } from "child_process"; +import { parse as parseShellArgs } from "shell-quote"; const execAsync = promisify(exec); @@ -22,14 +23,81 @@ export type ClaudeOptions = { fallbackModel?: string; timeoutMinutes?: string; model?: string; + // Raw, additional arguments forwarded verbatim to the Claude CLI. Mirrors the + // upstream v1 `claude_args` passthrough so downstream workflows can use newer + // CLI flags (e.g. `--json-schema`) without this fork enumerating each one. + claudeArgs?: string; }; type PreparedConfig = { claudeArgs: string[]; promptPath: string; env: Record; + // True when the forwarded claude_args request schema-validated output + // (`--json-schema`), which means we must surface `structured_output`. + hasJsonSchema: boolean; }; +/** + * Strip full comment lines (first non-whitespace char is `#`) from a shell + * argument string before tokenizing. Inline `#` inside a quoted value is left + * untouched — shell-quote handles quoting. Ported from upstream + * claude-code-base-action's claude_args parsing. + */ +function stripShellComments(input: string): string { + return input + .split("\n") + .filter((line) => !line.trim().startsWith("#")) + .join("\n"); +} + +/** + * Tokenize the raw `claude_args` string into individual CLI argument tokens + * using shell-quote, so quoted values (e.g. `--json-schema '{"$ref": ...}'`) + * survive as a single token. Non-string tokens (shell operators/globs) are + * dropped, matching upstream behavior. Returns [] for empty/whitespace input. + */ +export function parseClaudeArgs(claudeArgs?: string): string[] { + if (!claudeArgs?.trim()) { + return []; + } + return parseShellArgs(stripShellComments(claudeArgs)).filter( + (arg): arg is string => typeof arg === "string", + ); +} + +/** + * Locate the schema-validated result emitted by the Claude CLI. With + * `--output-format stream-json` the CLI prints one JSON object per line and the + * final `{"type":"result", ...}` event carries a `structured_output` field when + * `--json-schema` was supplied. We scan from the end (the result is last) and + * return the first `structured_output` we find. Returns undefined when absent. + */ +export function extractStructuredOutput(output: string): unknown { + const lines = output.split("\n"); + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i]?.trim(); + if (!line) { + continue; + } + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch { + continue; // Not a JSON line (e.g. human-readable log output) + } + if ( + parsed && + typeof parsed === "object" && + "structured_output" in parsed && + (parsed as { structured_output?: unknown }).structured_output != null + ) { + return (parsed as { structured_output: unknown }).structured_output; + } + } + return undefined; +} + function parseCustomEnvVars(claudeEnv?: string): Record { if (!claudeEnv || claudeEnv.trim() === "") { return {}; @@ -107,6 +175,16 @@ export function prepareRunConfig( } } + // Forward any additional raw CLI arguments verbatim, after the args this + // action manages. Empty/unset claude_args leaves the arg list byte-identical + // to before this feature, preserving behavior for existing callers. + const extraArgs = parseClaudeArgs(options.claudeArgs); + claudeArgs.push(...extraArgs); + + const hasJsonSchema = extraArgs.some( + (arg) => arg === "--json-schema" || arg.startsWith("--json-schema="), + ); + // Parse custom environment variables const customEnv = parseCustomEnvVars(options.claudeEnv); @@ -114,6 +192,7 @@ export function prepareRunConfig( claudeArgs, promptPath, env: customEnv, + hasJsonSchema, }; } @@ -309,8 +388,30 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) { core.warning(`Failed to process output for execution metrics: ${e}`); } - core.setOutput("conclusion", "success"); core.setOutput("execution_file", EXECUTION_FILE); + + // When claude_args requested schema-validated output, surface it as the + // `structured_output` action output. Fail loudly (rather than emit empty) + // if the schema was requested but no structured result came back, matching + // upstream claude-code-base-action behavior. + if (config.hasJsonSchema) { + const structuredOutput = extractStructuredOutput(output); + if (structuredOutput == null) { + core.setOutput("conclusion", "failure"); + core.setFailed( + "--json-schema was provided in claude_args but Claude did not return structured_output.", + ); + process.exit(1); + } + core.setOutput("structured_output", JSON.stringify(structuredOutput)); + const fieldCount = + typeof structuredOutput === "object" + ? Object.keys(structuredOutput as object).length + : 0; + core.info(`Set structured_output with ${fieldCount} field(s)`); + } + + core.setOutput("conclusion", "success"); } else { core.setOutput("conclusion", "failure"); diff --git a/base-action/test/run-claude.test.ts b/base-action/test/run-claude.test.ts index 7dcfb18..2d193bd 100644 --- a/base-action/test/run-claude.test.ts +++ b/base-action/test/run-claude.test.ts @@ -1,7 +1,12 @@ #!/usr/bin/env bun import { describe, test, expect } from "bun:test"; -import { prepareRunConfig, type ClaudeOptions } from "../src/run-claude"; +import { + prepareRunConfig, + parseClaudeArgs, + extractStructuredOutput, + type ClaudeOptions, +} from "../src/run-claude"; describe("prepareRunConfig", () => { test("should prepare config with basic arguments", () => { @@ -294,4 +299,127 @@ describe("prepareRunConfig", () => { expect(prepared.env).toEqual({}); }); }); + + describe("claude_args passthrough", () => { + test("empty/undefined claude_args leaves the arg list byte-identical", () => { + const baseline = prepareRunConfig("/tmp/test-prompt.txt", { + allowedTools: "Bash,Read", + maxTurns: "3", + }); + const withEmpty = prepareRunConfig("/tmp/test-prompt.txt", { + allowedTools: "Bash,Read", + maxTurns: "3", + claudeArgs: "", + }); + const withWhitespace = prepareRunConfig("/tmp/test-prompt.txt", { + allowedTools: "Bash,Read", + maxTurns: "3", + claudeArgs: " \n ", + }); + + const expected = [ + "-p", + "--verbose", + "--output-format", + "stream-json", + "--allowedTools", + "Bash,Read", + "--max-turns", + "3", + ]; + expect(baseline.claudeArgs).toEqual(expected); + expect(withEmpty.claudeArgs).toEqual(expected); + expect(withWhitespace.claudeArgs).toEqual(expected); + expect(baseline.hasJsonSchema).toBe(false); + }); + + test("claude_args tokens are appended after the managed arguments", () => { + const prepared = prepareRunConfig("/tmp/test-prompt.txt", { + maxTurns: "3", + claudeArgs: "--output-format json --json-schema /tmp/schema.json", + }); + + expect(prepared.claudeArgs).toEqual([ + "-p", + "--verbose", + "--output-format", + "stream-json", + "--max-turns", + "3", + "--output-format", + "json", + "--json-schema", + "/tmp/schema.json", + ]); + expect(prepared.hasJsonSchema).toBe(true); + }); + + test("a single-quoted inline JSON schema survives as one token (incl. $ref)", () => { + const schema = '{"$ref":"#/$defs/Verdict","type":"object"}'; + const tokens = parseClaudeArgs(`--json-schema '${schema}'`); + expect(tokens).toEqual(["--json-schema", schema]); + }); + + test("comment lines are stripped before tokenizing", () => { + const tokens = parseClaudeArgs( + "# leading comment\n--max-turns 5\n # indented comment\n--verbose", + ); + expect(tokens).toEqual(["--max-turns", "5", "--verbose"]); + }); + + test("hasJsonSchema is detected for both space and equals forms", () => { + expect( + prepareRunConfig("/tmp/test-prompt.txt", { + claudeArgs: "--json-schema /tmp/s.json", + }).hasJsonSchema, + ).toBe(true); + expect( + prepareRunConfig("/tmp/test-prompt.txt", { + claudeArgs: "--json-schema=/tmp/s.json", + }).hasJsonSchema, + ).toBe(true); + expect( + prepareRunConfig("/tmp/test-prompt.txt", { + claudeArgs: "--max-turns 3", + }).hasJsonSchema, + ).toBe(false); + }); + }); + + describe("extractStructuredOutput", () => { + test("returns structured_output from the stream-json result event", () => { + const verdict = { decision: "approve", score: 9 }; + const output = [ + JSON.stringify({ type: "system", subtype: "init" }), + JSON.stringify({ type: "assistant", message: { content: [] } }), + JSON.stringify({ + type: "result", + subtype: "success", + is_error: false, + structured_output: verdict, + }), + ].join("\n"); + + expect(extractStructuredOutput(output)).toEqual(verdict); + }); + + test("returns undefined when no structured_output is present", () => { + const output = [ + JSON.stringify({ type: "system", subtype: "init" }), + JSON.stringify({ type: "result", subtype: "success", is_error: false }), + ].join("\n"); + + expect(extractStructuredOutput(output)).toBeUndefined(); + }); + + test("ignores non-JSON lines and trailing whitespace", () => { + const verdict = { ok: true }; + const output = `Running Claude...\n${JSON.stringify({ + type: "result", + structured_output: verdict, + })}\n\n`; + + expect(extractStructuredOutput(output)).toEqual(verdict); + }); + }); }); diff --git a/bun.lock b/bun.lock index 805acbc..b600a9b 100644 --- a/bun.lock +++ b/bun.lock @@ -1,22 +1,25 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@anthropic-ai/claude-code-action", "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.1", + "@anthropic-ai/sdk": "^0.30.0", "@modelcontextprotocol/sdk": "^1.11.0", - "@octokit/graphql": "^8.2.2", - "@octokit/rest": "^21.1.1", + "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", "node-fetch": "^3.3.2", + "shell-quote": "^1.8.1", "zod": "^3.24.4", }, "devDependencies": { "@types/bun": "1.2.11", "@types/node": "^20.0.0", "@types/node-fetch": "^2.6.12", + "@types/shell-quote": "^1.7.5", "prettier": "3.5.3", "typescript": "^5.8.3", }, @@ -33,6 +36,8 @@ "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.30.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-nuKvp7wOIz6BFei8WrTdhmSsx5mwnArYyJgh4+vYu3V4J0Ltb8Xm3odPm51n1aSI0XxNCrDl7O88cxCtUdAkaw=="], + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@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=="], @@ -43,13 +48,13 @@ "@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/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=="], - "@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], + "@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], "@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=="], - "@octokit/plugin-request-log": ["@octokit/plugin-request-log@5.3.1", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw=="], + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="], @@ -57,9 +62,9 @@ "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], - "@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/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="], - "@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], + "@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], "@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="], @@ -69,8 +74,14 @@ "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], + "@types/shell-quote": ["@types/shell-quote@1.7.5", "", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "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=="], @@ -129,6 +140,8 @@ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], "eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="], @@ -137,8 +150,6 @@ "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=="], @@ -149,6 +160,10 @@ "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=="], + "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -171,6 +186,8 @@ "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -183,6 +200,8 @@ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "json-with-bigint": ["json-with-bigint@3.5.8", "", {}, "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -245,6 +264,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -257,6 +278,8 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], @@ -267,7 +290,7 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + "universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], @@ -275,7 +298,11 @@ "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=="], + "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -285,79 +312,69 @@ "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=="], + "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], - "@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - - "@octokit/endpoint/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], - - "@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - - "@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=="], + "@anthropic-ai/sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "@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.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-request-log/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], - "@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@octokit/rest/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], - "@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + "@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="], - "@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.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=="], - - "@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@13.5.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw=="], + "@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="], "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "express/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "type-is/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], - "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - - "@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], - - "@octokit/graphql/@octokit/request/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="], + "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], - "@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/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], - "@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/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], - "@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=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@10.0.10", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "content-type": "^2.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w=="], - "@octokit/plugin-request-log/@octokit/core/before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/plugin-request-log/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/plugin-request-log/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + + "@octokit/plugin-request-log/@octokit/core/universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], - "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], - "@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@octokit/rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], - "@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="], + "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.10", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "content-type": "^2.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w=="], - "@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@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], - "@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=="], + "@octokit/rest/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@octokit/rest/@octokit/core/universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], - "@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], @@ -367,12 +384,20 @@ "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "@octokit/plugin-request-log/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], - "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], - "@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], + + "@octokit/rest/@octokit/core/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + + "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], } } diff --git a/examples/gitea-structured-output.yml b/examples/gitea-structured-output.yml new file mode 100644 index 0000000..1b8b3a1 --- /dev/null +++ b/examples/gitea-structured-output.yml @@ -0,0 +1,64 @@ +# Example: schema-validated PR review verdict via `claude_args` + `structured_output`. +# +# Instead of having the agent write its verdict to a file, this asks Claude for a +# result that conforms to a JSON Schema and reads it back from the action output. +# Requires a Claude CLI new enough to support `--json-schema` (see README → +# "Structured output"). If you consume this action through a pre-baked runner +# image (path_to_claude_code_executable), that image's CLI must be bumped too. + +name: Claude PR Review (structured output) + +on: + pull_request: + types: [opened, synchronize] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # The schema the verdict must conform to. Using a file (rather than an + # inline string) keeps the value free of spaces and `$` characters, so a + # schema with $ref/$defs survives shell tokenization intact. + - name: Write review schema + shell: bash + run: | + mkdir -p .gitea + cat > .gitea/review-schema.json <<'JSON' + { + "type": "object", + "additionalProperties": false, + "required": ["decision", "summary"], + "properties": { + "decision": { "type": "string", "enum": ["approve", "request_changes", "comment"] }, + "summary": { "type": "string" } + } + } + JSON + + - name: Run Claude review + id: review + uses: ryan/claude-code-gitea-action@gitea + with: + gitea_token: ${{ secrets.GITEA_TOKEN }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + direct_prompt: | + Review the changes in this pull request and return your verdict + strictly matching the provided JSON schema. + claude_args: --json-schema ${{ github.workspace }}/.gitea/review-schema.json + + - name: Act on the verdict + shell: bash + env: + VERDICT: ${{ steps.review.outputs.structured_output }} + run: | + echo "Decision: $(echo "$VERDICT" | jq -r '.decision')" + echo "Summary: $(echo "$VERDICT" | jq -r '.summary')" diff --git a/package.json b/package.json index 67255da..2881221 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,14 @@ "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", "node-fetch": "^3.3.2", + "shell-quote": "^1.8.1", "zod": "^3.24.4" }, "devDependencies": { "@types/bun": "1.2.11", "@types/node": "^20.0.0", "@types/node-fetch": "^2.6.12", + "@types/shell-quote": "^1.7.5", "prettier": "3.5.3", "typescript": "^5.8.3" } -- 2.52.0