Compare commits

..

27 Commits

Author SHA1 Message Date
Colin McDonnell 5c8b03a427 Lock 2025-09-10 00:56:50 -07:00
Colin McDonnell 92225d30c5 No frozen lockfile 2025-09-10 00:36:03 -07:00
Colin McDonnell 3630ba6618 0.0.8 2025-09-10 00:32:54 -07:00
Colin McDonnell 6a03bb8e1b Update precommit 2025-09-10 00:31:21 -07:00
Colin McDonnell 3139f541e4 feat: integrate OIDC token exchange in GitHub Action
- Add setupGitHubInstallationToken utility for OIDC token generation
- Implement automatic token exchange with Pullfrog API endpoint
- Add support for multiple authentication methods (input, env, OIDC)
- Create setup utilities for test repository management
- Update action entry point to handle new token flow
- Add environment variable documentation for API key
- Remove large bundled dependencies and optimize build
- Support both development and production token workflows
2025-09-10 00:30:45 -07:00
Colin McDonnell c5b9c7cfc4 Tweak 2025-09-09 17:19:59 -07:00
Colin McDonnell 7a14716481 Disable npm publishing for now 2025-09-09 16:58:30 -07:00
Colin McDonnell 087709f4c7 Add lock 2025-09-09 16:55:45 -07:00
Colin McDonnell ff81db8bb7 Update 2025-09-09 16:54:36 -07:00
Colin McDonnell bfd948fd3c v0.0.7 2025-09-09 16:32:51 -07:00
Colin McDonnell 260b563913 Update workflow 2025-09-09 16:31:47 -07:00
Colin McDonnell 5fef548cee Drop lock 2025-09-09 16:26:18 -07:00
Colin McDonnell 7d633da1be refactor: complete action testing system overhaul
- Removed /scratch directory, now cloning pullfrogai/scratch as needed
- Implemented new play.ts testing system with local and Docker/act modes
- Added environment variable propagation to cloned test repositories
- Created minimal .act-dist approach to avoid pnpm symlink issues with Docker
- Migrated from dist/index.js to entry.cjs bundled output
- Added TypeScript fixture support with MainParams type safety
- Organized all test fixtures in fixtures/ directory
- Updated publish workflow to trigger on package.json changes
- Removed unnecessary INPUT_ANTHROPIC_API_KEY references
- Added comprehensive documentation for new testing system
- Fixed pre-commit hook to use entry.cjs instead of dist/
2025-09-09 16:20:00 -07:00
Colin McDonnell ede6cfdfbe Implement Claude Code basics 2025-08-29 00:41:05 -07:00
Colin McDonnell 7745a0befb Update Claude agent to use proper headless mode
- Replace --dangerously-skip-permissions with official headless mode flags
- Use -p (--print) for non-interactive mode
- Add --output-format json for structured responses
- Use --permission-mode acceptEdits for automation
- Parse JSON response and extract metadata (cost, duration, session_id)
- Handle both successful and error responses properly
- Reference: https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-headless
2025-08-28 13:42:04 -07:00
Colin McDonnell 1abbd7ff41 Refactor action with agent interface system and make anthropic_api_key optional
- Created extensible agent interface with install() and execute() methods
- Moved Claude Code logic to agents/claude.ts implementing Agent interface
- Added utilities directory for reusable functions (exec, files)
- Refactored index.ts to be minimal (35 lines) using agent abstraction
- Made anthropic_api_key optional in action.yml
- Updated Node.js imports to use node: prefix convention
- Bumped version to 0.0.5
- Architecture now supports multiple agents (OpenAI, Gemini, etc.)
2025-08-28 13:37:20 -07:00
Colin McDonnell 6a0d9cc244 Update release name. v0.0.4 2025-08-27 18:19:40 -07:00
Colin McDonnell ab468aa32f Tweak 2025-08-27 18:15:23 -07:00
Colin McDonnell ba61fc6679 fix: update pre-commit hook permissions 2025-08-27 18:14:56 -07:00
Colin McDonnell a226797098 feat: enhance message logging with 'Pullfrog says' prefix 2025-08-27 18:14:39 -07:00
Colin McDonnell c05a47d5d8 test: second empty commit to verify workflow 2025-08-27 18:14:10 -07:00
Colin McDonnell ec9f69c670 test: empty commit to test husky pre-commit hook 2025-08-27 18:14:05 -07:00
Colin McDonnell 9239b40372 Set up husky 2025-08-27 18:13:10 -07:00
Colin McDonnell f6efe56478 Tweak 2025-08-27 18:08:29 -07:00
Colin McDonnell 3d47375b24 Update lockfile 2025-08-27 18:06:59 -07:00
Colin McDonnell 609b022547 Switch from Rolldown to esbuild, build to index.cjs
- Replace Rolldown with esbuild for more reliable bundling
- Configure esbuild to output CommonJS to index.cjs
- Update action.yml to use index.cjs as main entry point
- Remove problematic rolldown.config.js
- Bump version to 0.0.3
- ESM codebase with CJS build output for GitHub Actions compatibility
2025-08-27 18:05:54 -07:00
Colin McDonnell ad2680524d Switch to esbuild 2025-08-27 18:05:17 -07:00
30 changed files with 28436 additions and 18442 deletions
-108
View File
@@ -1,108 +0,0 @@
name: Auto-tag Action Release
on:
push:
branches: [main]
paths:
- 'package.json'
permissions:
contents: write
jobs:
auto-tag:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Get package version
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
# Extract major version (e.g., "0" from "0.0.1")
MAJOR_VERSION=$(echo $VERSION | cut -d. -f1)
echo "major_tag=v$MAJOR_VERSION" >> $GITHUB_OUTPUT
- name: Check if tag already exists
id: check_tag
run: |
if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Tag ${{ steps.version.outputs.tag }} already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Tag ${{ steps.version.outputs.tag }} does not exist"
fi
- name: Install dependencies and build
if: steps.check_tag.outputs.exists == 'false'
run: |
pnpm install
pnpm run build
- name: Commit built files if changed
if: steps.check_tag.outputs.exists == 'false'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
if [[ -n $(git status --porcelain) ]]; then
git add dist/
git commit -m "chore: rebuild action for v${{ steps.version.outputs.version }}"
git push
fi
- name: Create and push tags
if: steps.check_tag.outputs.exists == 'false'
run: |
# Create specific version tag
git tag ${{ steps.version.outputs.tag }}
git push origin ${{ steps.version.outputs.tag }}
# Create/update major version tag (moving tag)
git tag -f ${{ steps.version.outputs.major_tag }}
git push origin ${{ steps.version.outputs.major_tag }} --force
- name: Create GitHub Release
if: steps.check_tag.outputs.exists == 'false'
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
release_name: Action Release ${{ steps.version.outputs.tag }}
body: |
Automated release for action version ${{ steps.version.outputs.version }}
## Usage
```yaml
- uses: pullfrog/pullfrog@${{ steps.version.outputs.major_tag }}
with:
message: "Your message here"
```
Or use the specific version:
```yaml
- uses: pullfrog/pullfrog@${{ steps.version.outputs.tag }}
with:
message: "Your message here"
```
draft: false
prerelease: false
+139
View File
@@ -0,0 +1,139 @@
name: Publish & Release
on:
push:
branches:
- main
paths:
- "package.json"
workflow_dispatch:
permissions:
contents: write
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Get package version
id: version
run: |
VERSION=$(npm pkg get version | tr -d '"')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
# Extract major version (e.g., "0" from "0.0.1")
MAJOR_VERSION=$(echo $VERSION | cut -d. -f1)
echo "major_tag=v$MAJOR_VERSION" >> $GITHUB_OUTPUT
echo "📦 Package version: $VERSION"
- name: Check if tag already exists
id: check_tag
run: |
if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "⚠️ Tag ${{ steps.version.outputs.tag }} already exists - skipping release"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "✅ Tag ${{ steps.version.outputs.tag }} does not exist - will create release"
fi
- name: Verify built files are up to date
if: steps.check_tag.outputs.exists == 'false'
run: |
# Check if there are any uncommitted changes
if [[ -n $(git status --porcelain) ]]; then
echo "❌ Error: There are uncommitted changes. Built files should be committed via pre-commit hook."
git status
exit 1
fi
echo "✅ All built files are up to date"
- name: Build for npm with zshy
if: steps.check_tag.outputs.exists == 'false'
run: pnpm build:npm
- name: Create and push tags
if: steps.check_tag.outputs.exists == 'false'
run: |
# Create specific version tag
git tag ${{ steps.version.outputs.tag }}
git push origin ${{ steps.version.outputs.tag }}
# Create/update major version tag (moving tag)
git tag -f ${{ steps.version.outputs.major_tag }}
git push origin ${{ steps.version.outputs.major_tag }} --force
echo "🏷️ Created tags: ${{ steps.version.outputs.tag }} and ${{ steps.version.outputs.major_tag }}"
- name: Create GitHub Release
if: steps.check_tag.outputs.exists == 'false'
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
release_name: "${{ steps.version.outputs.tag }}"
body: |
## 📦 @pullfrog/action ${{ steps.version.outputs.version }}
### Usage in GitHub Actions
```yaml
- uses: pullfrog/action@${{ steps.version.outputs.major_tag }}
```
### Installation via npm
```bash
npm install @pullfrog/action@${{ steps.version.outputs.version }}
```
draft: false
prerelease: false
# - name: Publish to npm
# if: steps.check_tag.outputs.exists == 'false'
# run: npm publish --access public
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Summary
if: always()
run: |
echo "## 📊 Publish Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ steps.check_tag.outputs.exists }}" == "true" ]]; then
echo "⚠️ Version ${{ steps.version.outputs.version }} already exists - no action taken" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Successfully published version ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🏷️ Tags Created" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ steps.version.outputs.tag }}\` (specific version)" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ steps.version.outputs.major_tag }}\` (major version, auto-updating)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Published to" >> $GITHUB_STEP_SUMMARY
echo "- GitHub Release: [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }})" >> $GITHUB_STEP_SUMMARY
echo "- npm Registry: [@pullfrog/action@${{ steps.version.outputs.version }}](https://www.npmjs.com/package/@pullfrog/action/v/${{ steps.version.outputs.version }})" >> $GITHUB_STEP_SUMMARY
fi
+12 -2
View File
@@ -1,4 +1,4 @@
# macOS settings file
# macOS settings file
.DS_Store
# Contains all your dependencies
@@ -34,4 +34,14 @@ yarn-error.log*
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
examples
examples
# Act temporary distribution directory
.act-dist/
# Temporary backup of node_modules
.node_modules_backup/
# Temporary directory for cloned repos
.temp/
dist
+6
View File
@@ -0,0 +1,6 @@
# Build the action before committing
echo "🔨 Building action..."
npm run build
# Add the built files to the commit
git add entry.cjs
+76 -2
View File
@@ -1,4 +1,78 @@
# Pullfrog
# Pullfrog Action
A simple GitHub Action that prints a customizable message to the console.
GitHub Action for running Claude Code and other agents via Pullfrog.
## Quick Start
```bash
# Install dependencies
pnpm install
# Test with default prompt
npm run play # Run locally on your machine
npm run play -- --act # Run in Docker (simulates GitHub Actions)
```
## Testing with play.ts
The `play.ts` script provides two ways to test the action:
### Local Mode (Default)
```bash
npm run play # Uses fixtures/play.txt
npm run play fixtures/complex.txt # Custom prompt file
```
- Clones the scratch repository to `.temp`
- Runs Claude Code directly on your machine
- Fast iteration for development
### Docker Mode (--act flag)
```bash
npm run play -- --act # Uses fixtures/play.txt
npm run play fixtures/simple.txt -- --act # Custom prompt file
```
- Builds fresh bundles with esbuild
- Creates minimal distribution without node_modules
- Runs in Docker container via `act`
- Simulates GitHub Actions environment
### Prompt Files
Supports `.txt`, `.json`, and `.ts` files:
```bash
npm run play prompt.txt # Plain text prompt
npm run play config.json # JSON configuration
npm run play dynamic.ts # TypeScript with default export
```
## Building
```bash
pnpm build # Production build (bundles & removes node_modules)
pnpm build:dev # Development build (keeps node_modules)
pnpm dev # Watch mode
```
The action is bundled into `entry.cjs` with all dependencies included, eliminating runtime dependency on node_modules.
## Environment Variables
Create `.env` in `/action`:
```bash
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Claude API key
```
## Architecture
- **entry.cjs**: Bundled action entry point (self-contained)
- **agents/**: Agent implementations (Claude, etc.)
- **utils/**: Utilities for subprocess, act, and formatting
- **fixtures/**: Test prompt files
## Why No node_modules?
pnpm uses symlinks that cause "invalid symlink" errors when `act` copies the action to Docker. Our solution:
1. Bundle everything into `entry.cjs`
2. Remove node_modules after building
3. Create minimal `.act-dist` for Docker testing
+19 -10
View File
@@ -1,17 +1,26 @@
name: 'Simple Message Action'
description: 'A simple GitHub Action that prints a message to the console'
author: 'Pullfrog'
name: "Pullfrog Claude Code Action"
description: "Execute Claude Code with a prompt using Anthropic API"
author: "Pullfrog"
inputs:
message:
description: 'Message to print to console'
prompt:
description: "Prompt to send to Claude Code"
required: true
default: 'Hello from Pullfrog Action!'
default: "Hello from Claude Code!"
anthropic_api_key:
description: "Anthropic API key for Claude Code authentication"
required: false
github_token:
description: "GitHub token for repository access"
required: false
github_installation_token:
description: "GitHub App installation token"
required: false
runs:
using: 'node20'
main: 'dist/index.js'
using: "node20"
main: "entry.cjs"
branding:
icon: 'message-circle'
color: 'blue'
icon: "code"
color: "orange"
+406
View File
@@ -0,0 +1,406 @@
import { access, constants } from "node:fs/promises";
import * as core from "@actions/core";
import { boxString, tableString } from "../utils";
import { spawn } from "../utils/subprocess";
import type { Agent, AgentConfig, AgentResult } from "./types";
/**
* Claude Code agent implementation
*/
export class ClaudeAgent implements Agent {
private apiKey: string;
public runStats = {
toolsUsed: 0,
turns: 0,
startTime: 0,
};
// $: ExecaMethod;
constructor(config: AgentConfig) {
if (!config.apiKey) {
throw new Error("Claude agent requires an API key");
}
this.apiKey = config.apiKey;
// Removed execa dependency - using spawn utility instead
}
/**
* Check if Claude Code CLI is already installed
*/
private async isClaudeInstalled(): Promise<boolean> {
try {
const claudePath = `${process.env.HOME}/.local/bin/claude`;
await access(claudePath, constants.F_OK | constants.X_OK);
return true;
} catch {
return false;
}
}
/**
* Install Claude Code CLI
*/
async install(): Promise<void> {
// Check if Claude Code is already installed
if (await this.isClaudeInstalled()) {
core.info("Claude Code is already installed, skipping installation");
return;
}
core.info("Installing Claude Code...");
try {
// Use shell execution to properly handle the pipe
const result = await spawn({
cmd: "bash",
args: [
"-c",
"curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93",
],
env: { ANTHROPIC_API_KEY: this.apiKey },
timeout: 120000, // 2 minute timeout
onStdout: (chunk) => {
// no logs
// process.stdout.write(chunk)
},
onStderr: (chunk) => process.stderr.write(chunk),
});
if (result.exitCode !== 0) {
throw new Error(
`Installation failed with exit code ${result.exitCode}: ${result.stderr}`,
);
}
core.info("Claude Code installed successfully");
} catch (error) {
throw new Error(`Failed to install Claude Code: ${error}`);
}
}
/**
* Execute Claude Code with the given prompt
*/
async execute(prompt: string): Promise<AgentResult> {
core.info("Running Claude Code...");
// printTable([[prompt]]);
try {
// Execute Claude Code with the prompt directly using proper headless mode
// core.info(`Executing Claude Code with prompt: ${prompt.substring(0, 100)}...`);
const claudePath = `${process.env.HOME}/.local/bin/claude`;
// console.log("Using Claude Code from:", claudePath);
console.log(boxString(prompt, { title: "Prompt" }));
const args = [
"--print",
"--output-format",
"stream-json",
"--verbose",
"--permission-mode",
"acceptEdits",
];
const env = {
ANTHROPIC_API_KEY: this.apiKey,
};
// Start a collapsible log group for streaming output
core.startGroup("🔄 Run details");
// Initialize run statistics
this.runStats = {
toolsUsed: 0,
turns: 0,
startTime: Date.now(),
};
const finalResult = "";
const totalCost = 0;
// run Claude Code with the prompt
const result = await spawn({
cmd: claudePath,
args,
env,
input: prompt,
timeout: 10 * 60 * 1000, // 10 minutes
onStdout: (_chunk) => {
// console.log(chunk);
processJSONChunk(_chunk, this);
},
onStderr: (_chunk) => {
if (_chunk.trim()) {
// core.warning(`[warn] ${chunk}`);
processJSONChunk(_chunk, this);
}
},
});
// throw on non-zero exit code
if (result.exitCode !== 0) {
throw new Error(
`Command failed with exit code ${result.exitCode}\n\nStdout: ${result.stdout}\n\nStderr: ${result.stderr}`,
);
}
// Process the complete buffered stdout to extract final results
// if (result.stdout.trim()) {
// const lines = result.stdout.trim().split("\n");
// for (const line of lines) {
// if (line.trim()) {
// const chunkResult = processJsonChunk(line);
// if (chunkResult.finalResult) finalResult = chunkResult.finalResult;
// if (chunkResult.totalCost) totalCost = chunkResult.totalCost;
// }
// }
// }
// Log run summary
const duration = Date.now() - this.runStats.startTime;
core.info(
`📊 Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration`,
);
core.info("✅ Task complete.");
core.endGroup(); // End the collapsible log group
return {
success: true,
output: finalResult,
metadata: {
promptLength: prompt.length,
exitCode: result.exitCode,
durationMs: result.durationMs,
totalCost,
},
};
} catch (error: any) {
// Ensure group is closed even if error occurs before group is started
try {
core.endGroup();
} catch {
// Group might not have been started, ignore
}
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
success: false,
error: `Failed to execute Claude Code: ${errorMessage}`,
};
}
}
}
/**
* Process a JSON chunk line and extract result data
*/
// function processJsonChunk(line: string): { finalResult?: string; totalCost?: number } {
// try {
// const chunk = JSON.parse(line.trim());
// processJSONChunk(chunk);
// // Collect final result and cost data
// if (chunk.type === "result" && chunk.result) {
// return {
// finalResult: chunk.result,
// totalCost: chunk.total_cost_usd || 0,
// };
// }
// return {};
// } catch {
// core.debug(`Failed to parse JSON line: ${line}`);
// return {};
// }
// }
/**
* Pretty print a JSON chunk based on its type
*/
function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
try {
// Parse the JSON string first
console.log(chunk);
const parsedChunk = JSON.parse(chunk.trim());
switch (parsedChunk.type) {
case "system":
if (parsedChunk.subtype === "init") {
core.info(`🚀 Starting Claude Code session...`);
// core.info(`📁 Working directory: ${parsedChunk.cwd}`);
// core.info(`🔑 Permission mode: ${parsedChunk.permissionMode}`);
core.info(
tableString([
["model", parsedChunk.model],
["cwd", parsedChunk.cwd],
["permission_mode", parsedChunk.permissionMode],
[
"tools",
parsedChunk.tools?.length
? `${parsedChunk.tools.length} tools`
: "none",
],
[
"mcp_servers",
parsedChunk.mcp_servers?.length
? `${parsedChunk.mcp_servers.length} servers`
: "none",
],
[
"slash_commands",
parsedChunk.slash_commands?.length
? `${parsedChunk.slash_commands.length} commands`
: "none",
],
]),
);
}
break;
case "assistant":
if (parsedChunk.message?.content) {
// Track turns
if (agent) {
agent.runStats.turns++;
}
for (const content of parsedChunk.message.content) {
if (content.type === "text") {
// Skip empty text content
if (content.text.trim()) {
core.info(
boxString(content.text.trim(), { title: "Claude Code" }),
);
}
} else if (content.type === "tool_use") {
// Track tools used
if (agent) {
agent.runStats.toolsUsed++;
}
// Enhanced tool usage logging
const toolName = content.name;
// const toolId = content.id;
core.info(`${toolName}`);
// Log tool-specific details based on tool type
if (content.input) {
const input = content.input;
// Common tool input fields
if (input.description) {
core.info(` └─ ${input.description}`);
}
// Tool-specific input fields
if (input.command) {
core.info(` └─ command: ${input.command}`);
}
if (input.file_path) {
core.info(` └─ file: ${input.file_path}`);
}
if (input.content) {
const contentPreview =
input.content.length > 100
? `${input.content.substring(0, 100)}...`
: input.content;
core.info(` └─ content: ${contentPreview}`);
}
if (input.query) {
core.info(` └─ query: ${input.query}`);
}
if (input.pattern) {
core.info(` └─ pattern: ${input.pattern}`);
}
if (input.url) {
core.info(` └─ url: ${input.url}`);
}
// For multi-edit or complex operations
if (input.edits && Array.isArray(input.edits)) {
core.info(` └─ edits: ${input.edits.length} changes`);
input.edits.forEach((edit: any, index: number) => {
if (edit.file_path) {
core.info(` ${index + 1}. ${edit.file_path}`);
}
});
}
// For task operations
if (input.task) {
core.info(` └─ task: ${input.task}`);
}
// For bash operations with specific details
if (input.bash_command) {
core.info(` └─ bash_command: ${input.bash_command}`);
}
}
// Log tool ID for debugging
// core.debug(` 🔗 Tool ID: ${toolId}`);
}
}
}
break;
case "user":
if (parsedChunk.message?.content) {
for (const content of parsedChunk.message.content) {
if (content.type === "tool_result") {
if (content.is_error) {
core.warning(`❌ Tool error: ${content.content}`);
} else {
// Enhanced tool result logging
const _resultContent = content.content.trim();
// do nothing for now. usually useless in headless more.
}
}
}
}
break;
case "result":
if (parsedChunk.subtype === "success") {
// Claude already prints something almost identical to this, so skip for now
// if (parsedChunk.result) {
// core.info(
// boxString(parsedChunk.result.trim(), {
// title: "🤖 Claude Code",
// maxWidth: 70,
// }),
// );
// }
core.info(
tableString([
[
"Cost",
`$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`,
],
["Input Tokens", parsedChunk.usage?.input_tokens || 0],
["Output Tokens", parsedChunk.usage?.output_tokens || 0],
["Duration", `${parsedChunk.duration_ms}ms`],
["Turns", parsedChunk.num_turns || 1],
]),
);
} else {
core.error(`❌ Failed: ${parsedChunk.error || "Unknown error"}`);
}
break;
default:
// Log unknown chunk types for debugging
core.debug(`📦 Unknown chunk type: ${parsedChunk.type}`);
break;
}
} catch (error) {
core.debug(`Failed to parse chunk: ${error}`);
core.debug(`Raw chunk: ${chunk.substring(0, 200)}...`);
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./claude";
export * from "./types";
+34
View File
@@ -0,0 +1,34 @@
/**
* Standard interface for all Pullfrog agents
*/
export interface Agent {
/**
* Install the agent and any required dependencies
*/
install(): Promise<void>;
/**
* Execute the agent with the given prompt
* @param prompt The prompt to send to the agent
* @param options Additional options specific to the agent
*/
execute(prompt: string, options?: Record<string, any>): Promise<AgentResult>;
}
/**
* Result returned by agent execution
*/
export interface AgentResult {
success: boolean;
output?: string;
error?: string;
metadata?: Record<string, any>;
}
/**
* Configuration for agent creation
*/
export interface AgentConfig {
apiKey?: string;
[key: string]: any;
}
-17731
View File
File diff suppressed because one or more lines are too long
Executable
+26063
View File
File diff suppressed because one or more lines are too long
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env node
/**
* Entry point for GitHub Action
* This file is bundled to entry.cjs and called directly by GitHub Actions
*/
import * as core from "@actions/core";
import { main } from "./main";
import { setupGitHubInstallationToken } from "./utils";
async function run(): Promise<void> {
try {
// Get inputs from GitHub Actions
const prompt = core.getInput("prompt", { required: true });
const anthropicApiKey = core.getInput("anthropic_api_key");
if (!prompt) {
throw new Error("prompt is required");
}
// Create params object with new structure
const inputs: any = {
prompt,
anthropic_api_key: anthropicApiKey,
};
// Add optional properties only if they exist
const githubToken = core.getInput("github_token") || process.env.GITHUB_TOKEN;
if (githubToken) {
inputs.github_token = githubToken;
}
const githubInstallationToken =
core.getInput("github_installation_token") || process.env.GITHUB_INSTALLATION_TOKEN;
if (githubInstallationToken) {
inputs.github_installation_token = githubInstallationToken;
} else {
// Setup GitHub installation token
await setupGitHubInstallationToken();
}
const params = {
inputs,
env: {} as Record<string, string>,
cwd: process.cwd(),
};
// Run the main function
const result = await main(params);
// TODO: Set outputs
if (!result.success) {
throw new Error(result.error || "Agent execution failed");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
core.setFailed(`Action failed: ${errorMessage}`);
}
}
// Run the action
run().catch((error) => {
console.error("Action failed:", error);
process.exit(1);
});
+16
View File
@@ -0,0 +1,16 @@
import { build } from "esbuild";
// Build the GitHub Action bundle only
// For npm package builds, use zshy (pnpm build:npm)
await build({
entryPoints: ["./entry.ts"],
bundle: true,
outfile: "./entry.cjs",
format: "cjs",
platform: "node",
target: "node20",
minify: false,
sourcemap: false,
});
console.log("✅ Build completed successfully!");
+13
View File
@@ -0,0 +1,13 @@
import type { MainParams } from "../main";
const testParams = {
inputs: {
prompt:
"List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it.",
anthropic_api_key: "sk-test-key",
},
env: {},
cwd: process.cwd(),
} satisfies MainParams;
export default testParams;
+1
View File
@@ -0,0 +1 @@
Print the list of tools available. Then create a new file called test.txt with the content "Hello from Pullfrog!".
+12 -17
View File
@@ -1,18 +1,13 @@
import * as core from '@actions/core';
/**
* Library entry point for npm package
* This exports the main function for programmatic usage
*/
try {
// Get the message input parameter, with a default fallback
const message = core.getInput('message') || 'Hello from Pullfrog Action!';
// Print the message to console and GitHub Actions logs
console.log(`🐸 ${message}`);
core.info(`Action executed successfully: ${message}`);
// Set an output for potential use by other actions
core.setOutput('message', message);
} catch (error) {
// Handle any errors and fail the action
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
core.setFailed(`Action failed: ${errorMessage}`);
}
export { ClaudeAgent } from "./agents";
export type { Agent, AgentConfig, AgentResult } from "./agents/types";
export {
type ExecutionInputs,
type MainParams,
type MainResult,
main,
} from "./main";
+72
View File
@@ -0,0 +1,72 @@
import * as core from "@actions/core";
import { ClaudeAgent } from "./agents";
// Expected environment variables that should be passed as inputs
export const EXPECTED_INPUTS: string[] = [
"ANTHROPIC_API_KEY",
"GITHUB_TOKEN",
"GITHUB_INSTALLATION_TOKEN"
];
export interface ExecutionInputs {
prompt: string;
anthropic_api_key: string;
github_token?: string;
github_installation_token?: string;
}
export interface MainParams {
inputs: ExecutionInputs;
env: Record<string, string>;
cwd: string;
}
export interface MainResult {
success: boolean;
output?: string | undefined;
error?: string | undefined;
}
export async function main(params: MainParams): Promise<MainResult> {
try {
// Extract inputs from params
const { inputs, env, cwd } = params;
// Set working directory if different from current
if (cwd !== process.cwd()) {
process.chdir(cwd);
}
// Set environment variables
Object.assign(process.env, env);
core.info(`→ Starting agent run with Claude Code`);
// Create and install the Claude agent
const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key });
await agent.install();
// Execute the agent with the prompt
const result = await agent.execute(inputs.prompt);
if (!result.success) {
return {
success: false,
error: result.error || "Agent execution failed",
output: result.output!,
};
}
return {
success: true,
output: result.output || "",
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
return {
success: false,
error: errorMessage,
};
}
}
-391
View File
@@ -1,391 +0,0 @@
{
"name": "action",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "action",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1"
},
"devDependencies": {
"@types/node": "^20.10.0",
"rolldown": "^0.12.0",
"typescript": "^5.3.0"
}
},
"node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
"license": "MIT"
},
"node_modules/@emnapi/core": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
"integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.4",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz",
"integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-0.12.2.tgz",
"integrity": "sha512-Y3Ajye63Z5KymGUwTLaK7Q6YMvycXqNiXtosecgVzjAwMITCmXdzgnWgzzx5UlWHMrDYL4m5zIeXGB5slLwoMA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-0.12.2.tgz",
"integrity": "sha512-ZcVuVFEFBXhp00TUNn+EDYs7SGGLQCznvCeuW1XkM8EC2/LfewU/o4WuJK7CBC4iCSktuFGpw7+zLe8D6iinzg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-0.12.2.tgz",
"integrity": "sha512-4fjuQHpm3q/Ly4fcqb8Qn49OQc2EQR2scUbQaOzXr7mIn9Zy8NfdRrsVG4/wpYvihIlTEtVx+ku0IZwcUzzZGg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.12.2.tgz",
"integrity": "sha512-MGEDaYLzTQ1kpvt13PzOwnd6O668S1mPM/vgi4O9vCfqJNTXZX8SeAg8Z2dQZbMSUyFDBVKGkz23GRTHqkGIsQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.12.2.tgz",
"integrity": "sha512-8uiaMe39twyIAw0do1Gc3O2SpQmyL1A/BucFncEB8eU5jtb3BWIM/X+F+eKDU6c+XZ+S1T025dhR1cGg8y20Xw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.12.2.tgz",
"integrity": "sha512-GOFSKaMJueaSSODcqI+0Hu79buHYtGV7h2tydIDkSDD20mQuvwOF7jIVd27yNdlXWS9wLObwEu3BNgHmIj1M6A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.12.2.tgz",
"integrity": "sha512-+hlRURUSiVP0+PFtjoTxUsiy/2NQpbf3DyUyMyl8Nv5+1BxjB6452VY1iFI+RzG4iLNJslcVcI6d6lJQ5zZYfw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-0.12.2.tgz",
"integrity": "sha512-9WKIaQSZZ0i9F5msW4I5kEj6ov+TZLteuTqCzI7nYWDBDm7m/hYkOkdIBf41GC8iKFsgVIQO0kRVAT966U8RxQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-0.12.2.tgz",
"integrity": "sha512-KY7bNrR3Jk6O0ne8LAaAnq9yI6xuKwhr/L2d1lBwrraCCyLHk0UEv4g6PfJkyUx6GfN0gVYuSxFgo9OggycldA==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^0.2.4"
},
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.12.2.tgz",
"integrity": "sha512-XYBXifPk5iNcPUTyV/yB4tlj5nI+fYoe/8CLHjyUG8GS0l8rCD+jKaAv3Jvz2T2erlkjEPqJXmzhUfBscOUo6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rolldown/binding-win32-ia32-msvc": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.12.2.tgz",
"integrity": "sha512-SV9LqvEc0d4gCLbkezH+UJ2uj2pTw3mwSKhFJEm6ir1lb05W6y9++m67NRs17fhudkFAz8iKlyPxKtZBKzAKOw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.12.2.tgz",
"integrity": "sha512-XnRqHx182tyk1M12OMXqLajIj9DZrOUEKSPYrQUSaMCmHHAhULYP6ki240CV16PBWZW4Q1pwQ7YVKYFevRtvKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
"integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "20.19.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz",
"integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/rolldown": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-0.12.2.tgz",
"integrity": "sha512-YJYKiYt2O9XytiQ3Na4Kk29avfIXhvK7udB3wAaVaF4kiSsFKE1167tElO/0eD6tjfJXCvwNxwsyYkBJRtsLmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"zod": "^3.23.8"
},
"bin": {
"rolldown": "bin/cli.js"
},
"optionalDependencies": {
"@rolldown/binding-darwin-arm64": "0.12.2",
"@rolldown/binding-darwin-x64": "0.12.2",
"@rolldown/binding-freebsd-x64": "0.12.2",
"@rolldown/binding-linux-arm-gnueabihf": "0.12.2",
"@rolldown/binding-linux-arm64-gnu": "0.12.2",
"@rolldown/binding-linux-arm64-musl": "0.12.2",
"@rolldown/binding-linux-x64-gnu": "0.12.2",
"@rolldown/binding-linux-x64-musl": "0.12.2",
"@rolldown/binding-wasm32-wasi": "0.12.2",
"@rolldown/binding-win32-arm64-msvc": "0.12.2",
"@rolldown/binding-win32-ia32-msvc": "0.12.2",
"@rolldown/binding-win32-x64-msvc": "0.12.2"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"license": "MIT",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+43 -11
View File
@@ -1,33 +1,65 @@
{
"name": "action",
"version": "0.0.2",
"main": "index.js",
"name": "@pullfrog/action",
"version": "0.0.8",
"type": "module",
"files": [
"index.js",
"index.cjs",
"index.d.ts",
"index.d.cts",
"agents",
"utils",
"main.js",
"main.d.ts"
],
"directories": {
"example": "examples"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rolldown -c rolldown.config.js",
"dev": "rolldown -c --watch"
"typecheck": "tsc --noEmit",
"build": "node esbuild.config.js",
"build:npm": "zshy",
"build:dev": "node esbuild.config.js",
"prepare": "husky",
"play": "tsx play.ts"
},
"dependencies": {
"@actions/core": "^1.10.1"
"@actions/core": "^1.11.1",
"dotenv": "^17.2.2",
"execa": "^9.6.0",
"table": "^6.9.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"commander": "^14.0.0",
"esbuild": "^0.25.9",
"husky": "^9.0.0",
"typescript": "^5.3.0",
"rolldown": "^0.12.0"
"zshy": "^0.4.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pullfrog/pullfrog.git"
"url": "git+https://github.com/pullfrog/action.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"bugs": {
"url": "https://github.com/pullfrog/pullfrog/issues"
"url": "https://github.com/pullfrog/action/issues"
},
"homepage": "https://github.com/pullfrog/pullfrog#readme"
"homepage": "https://github.com/pullfrog/action#readme",
"zshy": {
"exports": "./index.ts"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.cts",
"exports": {
".": {
"types": "./dist/index.d.cts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
+160
View File
@@ -0,0 +1,160 @@
import { existsSync, readFileSync } from "node:fs";
import { dirname, extname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { Command } from "commander";
import { config } from "dotenv";
import { main } from "./main";
import { runAct } from "./utils/act";
import { setupTestRepo } from "./utils/setup";
// Load environment variables from .env file
config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
async function loadPrompt(filePath: string): Promise<string> {
const ext = extname(filePath).toLowerCase();
// Try to resolve the file path
let resolvedPath: string;
// First try as fixtures path
const fixturesPath = join(__dirname, "fixtures", filePath);
if (existsSync(fixturesPath)) {
resolvedPath = fixturesPath;
} else if (existsSync(filePath)) {
resolvedPath = resolve(filePath);
} else {
throw new Error(`File not found: ${filePath}`);
}
switch (ext) {
case ".txt": {
// Plain text - pass directly as prompt
return readFileSync(resolvedPath, "utf8").trim();
}
case ".json": {
// JSON - stringify and pass as prompt
const content = readFileSync(resolvedPath, "utf8");
const parsed = JSON.parse(content);
return JSON.stringify(parsed, null, 2);
}
case ".ts": {
// TypeScript - dynamic import and stringify default export
const fileUrl = pathToFileURL(resolvedPath).href;
const module = await import(fileUrl);
if (!module.default) {
throw new Error(`TypeScript file ${filePath} must have a default export`);
}
// If it's a string, use it directly
if (typeof module.default === "string") {
return module.default;
}
// If it's a MainParams object with a prompt field, extract the prompt
if (typeof module.default === "object" && module.default.prompt) {
return module.default.prompt;
}
// Otherwise stringify it
return JSON.stringify(module.default, null, 2);
}
default:
throw new Error(`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`);
}
}
async function runPlay(filePath: string, options: { act?: boolean }): Promise<void> {
try {
// Load the prompt from the specified file
const prompt = await loadPrompt(filePath);
if (options.act) {
// Use Docker/act to run the action
console.log("🐳 Running with Docker/act...");
runAct(prompt);
} else {
// Setup test repository and run directly
const tempDir = join(process.cwd(), ".temp");
setupTestRepo({ tempDir, forceClean: true });
// Change to the temp directory
process.chdir(tempDir);
console.log("🚀 Running test in .temp directory...");
console.log("─".repeat(50));
console.log(`Prompt from ${filePath}:`);
console.log(prompt);
console.log("─".repeat(50));
// Set environment variables from our .env for the action to use
const { EXPECTED_INPUTS } = await import("./main");
EXPECTED_INPUTS.forEach((inputName) => {
const value = process.env[inputName];
if (value) {
process.env[`INPUT_${inputName.toLowerCase()}`] = value;
}
});
// Run main with the new params structure
const inputs: any = {
prompt,
anthropic_api_key: process.env.ANTHROPIC_API_KEY || "",
};
// Add optional properties only if they exist
if (process.env.GITHUB_TOKEN) {
inputs.github_token = process.env.GITHUB_TOKEN;
}
if (process.env.GITHUB_INSTALLATION_TOKEN) {
inputs.github_installation_token = process.env.GITHUB_INSTALLATION_TOKEN;
}
const result = await main({
inputs,
env: process.env as Record<string, string>,
cwd: process.cwd(),
});
if (result.success) {
console.log("✅ Test completed successfully");
if (result.output) {
console.log("Output:", result.output);
}
} else {
console.error("❌ Test failed:", result.error);
process.exit(1);
}
}
} catch (error) {
console.error("❌ Error:", (error as Error).message);
process.exit(1);
}
}
// Set up CLI
const program = new Command();
program
.name("play")
.description("Test the Pullfrog action with various prompts")
.version("1.0.0")
.argument("[file]", "Prompt file to use (.txt, .json, or .ts)", "fixtures/basic.txt")
.option("--act", "Use Docker/act to run the action instead of running directly")
.action(async (file: string, options: { act?: boolean }) => {
await runPlay(file, options);
});
// Parse arguments and run
program.parseAsync(process.argv).catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
+751 -142
View File
@@ -9,18 +9,36 @@ importers:
.:
dependencies:
'@actions/core':
specifier: ^1.10.1
specifier: ^1.11.1
version: 1.11.1
dotenv:
specifier: ^17.2.2
version: 17.2.2
execa:
specifier: ^9.6.0
version: 9.6.0
table:
specifier: ^6.9.0
version: 6.9.0
devDependencies:
'@types/node':
specifier: ^20.10.0
version: 20.19.11
rolldown:
specifier: ^0.12.0
version: 0.12.2
version: 20.19.13
commander:
specifier: ^14.0.0
version: 14.0.0
esbuild:
specifier: ^0.25.9
version: 0.25.9
husky:
specifier: ^9.0.0
version: 9.1.7
typescript:
specifier: ^5.3.0
version: 5.9.2
zshy:
specifier: ^0.4.1
version: 0.4.1(typescript@5.9.2)
packages:
@@ -36,94 +54,417 @@ packages:
'@actions/io@1.1.3':
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
'@emnapi/core@1.4.5':
resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
'@esbuild/aix-ppc64@0.25.9':
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@emnapi/runtime@1.4.5':
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
'@esbuild/android-arm64@0.25.9':
resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@emnapi/wasi-threads@1.0.4':
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
'@esbuild/android-arm@0.25.9':
resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.25.9':
resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.25.9':
resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.25.9':
resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.25.9':
resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.9':
resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.25.9':
resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.25.9':
resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.25.9':
resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.25.9':
resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.25.9':
resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.25.9':
resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.25.9':
resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.25.9':
resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.25.9':
resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.9':
resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.9':
resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.9':
resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.9':
resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.9':
resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.25.9':
resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.25.9':
resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.25.9':
resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.25.9':
resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@fastify/busboy@2.1.1':
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
'@rolldown/binding-darwin-arm64@0.12.2':
resolution: {integrity: sha512-Y3Ajye63Z5KymGUwTLaK7Q6YMvycXqNiXtosecgVzjAwMITCmXdzgnWgzzx5UlWHMrDYL4m5zIeXGB5slLwoMA==}
cpu: [arm64]
os: [darwin]
'@nodelib/fs.stat@2.0.5':
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
'@rolldown/binding-darwin-x64@0.12.2':
resolution: {integrity: sha512-ZcVuVFEFBXhp00TUNn+EDYs7SGGLQCznvCeuW1XkM8EC2/LfewU/o4WuJK7CBC4iCSktuFGpw7+zLe8D6iinzg==}
cpu: [x64]
os: [darwin]
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@rolldown/binding-freebsd-x64@0.12.2':
resolution: {integrity: sha512-4fjuQHpm3q/Ly4fcqb8Qn49OQc2EQR2scUbQaOzXr7mIn9Zy8NfdRrsVG4/wpYvihIlTEtVx+ku0IZwcUzzZGg==}
cpu: [x64]
os: [freebsd]
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
'@rolldown/binding-linux-arm-gnueabihf@0.12.2':
resolution: {integrity: sha512-MGEDaYLzTQ1kpvt13PzOwnd6O668S1mPM/vgi4O9vCfqJNTXZX8SeAg8Z2dQZbMSUyFDBVKGkz23GRTHqkGIsQ==}
cpu: [arm]
os: [linux]
'@sindresorhus/merge-streams@2.3.0':
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
engines: {node: '>=18'}
'@rolldown/binding-linux-arm64-gnu@0.12.2':
resolution: {integrity: sha512-8uiaMe39twyIAw0do1Gc3O2SpQmyL1A/BucFncEB8eU5jtb3BWIM/X+F+eKDU6c+XZ+S1T025dhR1cGg8y20Xw==}
cpu: [arm64]
os: [linux]
'@sindresorhus/merge-streams@4.0.0':
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
'@rolldown/binding-linux-arm64-musl@0.12.2':
resolution: {integrity: sha512-GOFSKaMJueaSSODcqI+0Hu79buHYtGV7h2tydIDkSDD20mQuvwOF7jIVd27yNdlXWS9wLObwEu3BNgHmIj1M6A==}
cpu: [arm64]
os: [linux]
'@types/node@20.19.13':
resolution: {integrity: sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==}
'@rolldown/binding-linux-x64-gnu@0.12.2':
resolution: {integrity: sha512-+hlRURUSiVP0+PFtjoTxUsiy/2NQpbf3DyUyMyl8Nv5+1BxjB6452VY1iFI+RzG4iLNJslcVcI6d6lJQ5zZYfw==}
cpu: [x64]
os: [linux]
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
'@rolldown/binding-linux-x64-musl@0.12.2':
resolution: {integrity: sha512-9WKIaQSZZ0i9F5msW4I5kEj6ov+TZLteuTqCzI7nYWDBDm7m/hYkOkdIBf41GC8iKFsgVIQO0kRVAT966U8RxQ==}
cpu: [x64]
os: [linux]
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
'@rolldown/binding-wasm32-wasi@0.12.2':
resolution: {integrity: sha512-KY7bNrR3Jk6O0ne8LAaAnq9yI6xuKwhr/L2d1lBwrraCCyLHk0UEv4g6PfJkyUx6GfN0gVYuSxFgo9OggycldA==}
engines: {node: '>=14.21.3'}
cpu: [wasm32]
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
'@rolldown/binding-win32-arm64-msvc@0.12.2':
resolution: {integrity: sha512-XYBXifPk5iNcPUTyV/yB4tlj5nI+fYoe/8CLHjyUG8GS0l8rCD+jKaAv3Jvz2T2erlkjEPqJXmzhUfBscOUo6g==}
cpu: [arm64]
os: [win32]
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
'@rolldown/binding-win32-ia32-msvc@0.12.2':
resolution: {integrity: sha512-SV9LqvEc0d4gCLbkezH+UJ2uj2pTw3mwSKhFJEm6ir1lb05W6y9++m67NRs17fhudkFAz8iKlyPxKtZBKzAKOw==}
cpu: [ia32]
os: [win32]
astral-regex@2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'}
'@rolldown/binding-win32-x64-msvc@0.12.2':
resolution: {integrity: sha512-XnRqHx182tyk1M12OMXqLajIj9DZrOUEKSPYrQUSaMCmHHAhULYP6ki240CV16PBWZW4Q1pwQ7YVKYFevRtvKg==}
cpu: [x64]
os: [win32]
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
'@tybys/wasm-util@0.10.0':
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
'@types/node@20.19.11':
resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
rolldown@0.12.2:
resolution: {integrity: sha512-YJYKiYt2O9XytiQ3Na4Kk29avfIXhvK7udB3wAaVaF4kiSsFKE1167tElO/0eD6tjfJXCvwNxwsyYkBJRtsLmQ==}
commander@14.0.0:
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
engines: {node: '>=20'}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
dotenv@17.2.2:
resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==}
engines: {node: '>=12'}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
esbuild@0.25.9:
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
engines: {node: '>=18'}
hasBin: true
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
execa@9.6.0:
resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}
engines: {node: ^18.19.0 || >=20.5.0}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
get-stream@9.0.1:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
globby@14.1.0:
resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==}
engines: {node: '>=18'}
human-signals@8.0.1:
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
engines: {node: '>=18.18.0'}
husky@9.1.7:
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
engines: {node: '>=18'}
hasBin: true
ignore@7.0.5:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-plain-obj@4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
is-stream@4.0.1:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
lodash.truncate@4.4.2:
resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
npm-run-path@6.0.0:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
parse-ms@4.0.0:
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
engines: {node: '>=18'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-key@4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
path-type@6.0.0:
resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==}
engines: {node: '>=18'}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
pretty-ms@9.2.0:
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
engines: {node: '>=18'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
slash@5.1.0:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
slice-ansi@4.0.0:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-final-newline@4.0.0:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
table@6.9.0:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tunnel@0.0.6:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
@@ -141,8 +482,24 @@ packages:
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
engines: {node: '>=14.0'}
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
unicorn-magic@0.3.0:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
engines: {node: '>=18'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
yoctocolors@2.1.2:
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
engines: {node: '>=18'}
zshy@0.4.1:
resolution: {integrity: sha512-9BP1xv9BK4xNf8N5K85q+n81uFRT4p92JrT2BShXwgn8iw63QeSbdd7Ytw5RzqFG3AeIGtqpOEJPk4iiM77dsw==}
hasBin: true
peerDependencies:
typescript: '>5.5.0'
snapshots:
@@ -162,97 +519,336 @@ snapshots:
'@actions/io@1.1.3': {}
'@emnapi/core@1.4.5':
dependencies:
'@emnapi/wasi-threads': 1.0.4
tslib: 2.8.1
'@esbuild/aix-ppc64@0.25.9':
optional: true
'@emnapi/runtime@1.4.5':
dependencies:
tslib: 2.8.1
'@esbuild/android-arm64@0.25.9':
optional: true
'@emnapi/wasi-threads@1.0.4':
dependencies:
tslib: 2.8.1
'@esbuild/android-arm@0.25.9':
optional: true
'@esbuild/android-x64@0.25.9':
optional: true
'@esbuild/darwin-arm64@0.25.9':
optional: true
'@esbuild/darwin-x64@0.25.9':
optional: true
'@esbuild/freebsd-arm64@0.25.9':
optional: true
'@esbuild/freebsd-x64@0.25.9':
optional: true
'@esbuild/linux-arm64@0.25.9':
optional: true
'@esbuild/linux-arm@0.25.9':
optional: true
'@esbuild/linux-ia32@0.25.9':
optional: true
'@esbuild/linux-loong64@0.25.9':
optional: true
'@esbuild/linux-mips64el@0.25.9':
optional: true
'@esbuild/linux-ppc64@0.25.9':
optional: true
'@esbuild/linux-riscv64@0.25.9':
optional: true
'@esbuild/linux-s390x@0.25.9':
optional: true
'@esbuild/linux-x64@0.25.9':
optional: true
'@esbuild/netbsd-arm64@0.25.9':
optional: true
'@esbuild/netbsd-x64@0.25.9':
optional: true
'@esbuild/openbsd-arm64@0.25.9':
optional: true
'@esbuild/openbsd-x64@0.25.9':
optional: true
'@esbuild/openharmony-arm64@0.25.9':
optional: true
'@esbuild/sunos-x64@0.25.9':
optional: true
'@esbuild/win32-arm64@0.25.9':
optional: true
'@esbuild/win32-ia32@0.25.9':
optional: true
'@esbuild/win32-x64@0.25.9':
optional: true
'@fastify/busboy@2.1.1': {}
'@napi-rs/wasm-runtime@0.2.12':
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@emnapi/core': 1.4.5
'@emnapi/runtime': 1.4.5
'@tybys/wasm-util': 0.10.0
optional: true
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
'@rolldown/binding-darwin-arm64@0.12.2':
optional: true
'@nodelib/fs.stat@2.0.5': {}
'@rolldown/binding-darwin-x64@0.12.2':
optional: true
'@rolldown/binding-freebsd-x64@0.12.2':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@0.12.2':
optional: true
'@rolldown/binding-linux-arm64-gnu@0.12.2':
optional: true
'@rolldown/binding-linux-arm64-musl@0.12.2':
optional: true
'@rolldown/binding-linux-x64-gnu@0.12.2':
optional: true
'@rolldown/binding-linux-x64-musl@0.12.2':
optional: true
'@rolldown/binding-wasm32-wasi@0.12.2':
'@nodelib/fs.walk@1.2.8':
dependencies:
'@napi-rs/wasm-runtime': 0.2.12
optional: true
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@rolldown/binding-win32-arm64-msvc@0.12.2':
optional: true
'@sec-ant/readable-stream@0.4.1': {}
'@rolldown/binding-win32-ia32-msvc@0.12.2':
optional: true
'@sindresorhus/merge-streams@2.3.0': {}
'@rolldown/binding-win32-x64-msvc@0.12.2':
optional: true
'@sindresorhus/merge-streams@4.0.0': {}
'@tybys/wasm-util@0.10.0':
dependencies:
tslib: 2.8.1
optional: true
'@types/node@20.19.11':
'@types/node@20.19.13':
dependencies:
undici-types: 6.21.0
rolldown@0.12.2:
ajv@8.17.1:
dependencies:
zod: 3.25.76
optionalDependencies:
'@rolldown/binding-darwin-arm64': 0.12.2
'@rolldown/binding-darwin-x64': 0.12.2
'@rolldown/binding-freebsd-x64': 0.12.2
'@rolldown/binding-linux-arm-gnueabihf': 0.12.2
'@rolldown/binding-linux-arm64-gnu': 0.12.2
'@rolldown/binding-linux-arm64-musl': 0.12.2
'@rolldown/binding-linux-x64-gnu': 0.12.2
'@rolldown/binding-linux-x64-musl': 0.12.2
'@rolldown/binding-wasm32-wasi': 0.12.2
'@rolldown/binding-win32-arm64-msvc': 0.12.2
'@rolldown/binding-win32-ia32-msvc': 0.12.2
'@rolldown/binding-win32-x64-msvc': 0.12.2
fast-deep-equal: 3.1.3
fast-uri: 3.1.0
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
tslib@2.8.1:
optional: true
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
arg@5.0.2: {}
astral-regex@2.0.0: {}
braces@3.0.3:
dependencies:
fill-range: 7.1.1
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
commander@14.0.0: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
dotenv@17.2.2: {}
emoji-regex@8.0.0: {}
esbuild@0.25.9:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.9
'@esbuild/android-arm': 0.25.9
'@esbuild/android-arm64': 0.25.9
'@esbuild/android-x64': 0.25.9
'@esbuild/darwin-arm64': 0.25.9
'@esbuild/darwin-x64': 0.25.9
'@esbuild/freebsd-arm64': 0.25.9
'@esbuild/freebsd-x64': 0.25.9
'@esbuild/linux-arm': 0.25.9
'@esbuild/linux-arm64': 0.25.9
'@esbuild/linux-ia32': 0.25.9
'@esbuild/linux-loong64': 0.25.9
'@esbuild/linux-mips64el': 0.25.9
'@esbuild/linux-ppc64': 0.25.9
'@esbuild/linux-riscv64': 0.25.9
'@esbuild/linux-s390x': 0.25.9
'@esbuild/linux-x64': 0.25.9
'@esbuild/netbsd-arm64': 0.25.9
'@esbuild/netbsd-x64': 0.25.9
'@esbuild/openbsd-arm64': 0.25.9
'@esbuild/openbsd-x64': 0.25.9
'@esbuild/openharmony-arm64': 0.25.9
'@esbuild/sunos-x64': 0.25.9
'@esbuild/win32-arm64': 0.25.9
'@esbuild/win32-ia32': 0.25.9
'@esbuild/win32-x64': 0.25.9
execa@9.6.0:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
cross-spawn: 7.0.6
figures: 6.1.0
get-stream: 9.0.1
human-signals: 8.0.1
is-plain-obj: 4.1.0
is-stream: 4.0.1
npm-run-path: 6.0.0
pretty-ms: 9.2.0
signal-exit: 4.1.0
strip-final-newline: 4.0.0
yoctocolors: 2.1.2
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.8
fast-uri@3.1.0: {}
fastq@1.19.1:
dependencies:
reusify: 1.1.0
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
get-stream@9.0.1:
dependencies:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
globby@14.1.0:
dependencies:
'@sindresorhus/merge-streams': 2.3.0
fast-glob: 3.3.3
ignore: 7.0.5
path-type: 6.0.0
slash: 5.1.0
unicorn-magic: 0.3.0
human-signals@8.0.1: {}
husky@9.1.7: {}
ignore@7.0.5: {}
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
is-plain-obj@4.1.0: {}
is-stream@4.0.1: {}
is-unicode-supported@2.1.0: {}
isexe@2.0.0: {}
json-schema-traverse@1.0.0: {}
lodash.truncate@4.4.2: {}
merge2@1.4.1: {}
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
npm-run-path@6.0.0:
dependencies:
path-key: 4.0.0
unicorn-magic: 0.3.0
parse-ms@4.0.0: {}
path-key@3.1.1: {}
path-key@4.0.0: {}
path-type@6.0.0: {}
picomatch@2.3.1: {}
pretty-ms@9.2.0:
dependencies:
parse-ms: 4.0.0
queue-microtask@1.2.3: {}
require-from-string@2.0.2: {}
reusify@1.1.0: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
slash@5.1.0: {}
slice-ansi@4.0.0:
dependencies:
ansi-styles: 4.3.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-final-newline@4.0.0: {}
table@6.9.0:
dependencies:
ajv: 8.17.1
lodash.truncate: 4.4.2
slice-ansi: 4.0.0
string-width: 4.2.3
strip-ansi: 6.0.1
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
tunnel@0.0.6: {}
@@ -264,4 +860,17 @@ snapshots:
dependencies:
'@fastify/busboy': 2.1.1
zod@3.25.76: {}
unicorn-magic@0.3.0: {}
which@2.0.2:
dependencies:
isexe: 2.0.0
yoctocolors@2.1.2: {}
zshy@0.4.1(typescript@5.9.2):
dependencies:
arg: 5.0.2
globby: 14.1.0
table: 6.9.0
typescript: 5.9.2
-15
View File
@@ -1,15 +0,0 @@
import { defineConfig } from 'rolldown';
export default defineConfig({
input: './index.ts',
output: {
file: './bundle.js',
format: 'esm'
},
platform: 'node',
target: 'node20',
external: (id) => {
// Mark all node modules as external
return id.includes('node_modules') || id.startsWith('node:');
}
});
+41 -13
View File
@@ -1,17 +1,45 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./",
"rootDir": "./",
// File Layout
// "rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "esnext",
"moduleResolution": "bundler",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": false,
"sourceMap": false
},
"include": ["*.ts"],
"exclude": ["node_modules", "**/*.test.ts"]
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true
}
}
+89
View File
@@ -0,0 +1,89 @@
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { config } from "dotenv";
import { buildAction, setupTestRepo } from "./setup";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Environment variables that should be passed as secrets to the workflow
const ENV_VARS = ["ANTHROPIC_API_KEY", "GITHUB_INSTALLATION_TOKEN"];
export function runAct(prompt: string): void {
const tempDir = join(__dirname, "..", ".temp");
const actionPath = join(__dirname, "..");
const envPath = join(__dirname, "..", "..", ".env");
// Setup test repository
setupTestRepo({ tempDir });
// Load environment variables
config({ path: envPath });
// Build action bundles
buildAction(actionPath);
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
// Create minimal dist for act (avoids pnpm symlink issues)
const distPath = join(actionPath, ".act-dist");
console.log("📦 Creating minimal distribution for act...");
execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: "/bin/bash" });
// Copy only necessary files (bundled, no node_modules needed)
["action.yml", "entry.cjs", "index.cjs", "package.json"].forEach((file) => {
const src = join(actionPath, file);
if (existsSync(src)) {
execSync(`cp "${src}" "${distPath}"`);
}
});
try {
// Build the act command with input directly
// Properly escape the prompt for shell
const escapedPrompt = prompt.replace(/'/g, "'\\''");
const actCommandParts = [
"act",
"workflow_dispatch",
"-W",
workflowPath,
"--input",
`prompt='${escapedPrompt}'`,
"--local-repository",
`pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks
];
// Add environment variables as secrets that will be available to the workflow
ENV_VARS.forEach((key) => {
if (process.env[key]) {
actCommandParts.push("-s", key);
}
});
// We only need the specific ENV_VARS, no need to add other variables
const actCommand = actCommandParts.join(" ");
console.log("🚀 Running act with prompt:");
console.log("─".repeat(50));
console.log(prompt);
console.log("─".repeat(50));
console.log("");
// Execute act
execSync(actCommand, {
stdio: "inherit",
cwd: join(__dirname, "..", ".."),
});
// Clean up
execSync(`rm -rf "${distPath}"`);
} catch (error) {
// Clean up on error
execSync(`rm -rf "${distPath}"`);
console.error("❌ Act execution failed:", (error as Error).message);
process.exit(1);
}
}
+13
View File
@@ -0,0 +1,13 @@
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
/**
* Create a temporary file with the given content
*/
export function createTempFile(content: string, filename = "temp.txt"): string {
const tempDir = mkdtempSync(join(tmpdir(), "pullfrog-"));
const filePath = join(tempDir, filename);
writeFileSync(filePath, content);
return filePath;
}
+56
View File
@@ -0,0 +1,56 @@
import * as core from "@actions/core";
/**
* Setup GitHub installation token for the action
*/
export async function setupGitHubInstallationToken(): Promise<string> {
// Check if we have an installation token from inputs or environment
const inputToken = core.getInput("github_installation_token");
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
const existingToken = inputToken || envToken;
if (existingToken) {
core.info("Using provided GitHub installation token");
return existingToken;
}
core.info("No cached installation token found, generating OIDC token...");
try {
// Generate OIDC token for our API
const oidcToken = await core.getIDToken("pullfrog-api");
core.info("OIDC token generated successfully");
// Exchange OIDC token for installation token
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
method: "POST",
headers: {
Authorization: `Bearer ${oidcToken}`,
"Content-Type": "application/json",
},
});
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
throw new Error(
`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}`
);
}
const tokenData = await tokenResponse.json();
core.info(
`Installation token obtained for ${tokenData.owner}/${tokenData.repository || "all repositories"}`
);
// Set the token as an environment variable for this run
process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token;
return tokenData.token;
} catch (error) {
throw new Error(
`Failed to setup GitHub installation token: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from "./files";
export * from "./github";
export * from "./setup";
export * from "./subprocess";
export * from "./table";
+51
View File
@@ -0,0 +1,51 @@
import { execSync } from "node:child_process";
import { existsSync, rmSync } from "node:fs";
export interface SetupOptions {
tempDir: string;
repoUrl?: string;
forceClean?: boolean;
}
/**
* Setup the test repository for running actions
*/
export function setupTestRepo(options: SetupOptions): void {
const {
tempDir,
repoUrl = "git@github.com:pullfrogai/scratch.git",
forceClean = false,
} = options;
// Handle existing temp directory
if (existsSync(tempDir)) {
if (forceClean) {
console.log("🗑️ Removing existing .temp directory...");
rmSync(tempDir, { recursive: true, force: true });
// Clone the repository
console.log("📦 Cloning pullfrogai/scratch into .temp...");
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
} else {
console.log("📦 Resetting existing .temp repository...");
execSync("git reset --hard HEAD && git clean -fd", {
cwd: tempDir,
stdio: "inherit",
});
}
} else {
console.log("📦 Cloning pullfrogai/scratch into .temp...");
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
}
}
/**
* Build the action bundles
*/
export function buildAction(actionPath: string): void {
console.log("🔨 Building fresh bundles with esbuild...");
execSync("node esbuild.config.js", {
cwd: actionPath,
stdio: "inherit",
});
}
+117
View File
@@ -0,0 +1,117 @@
import { spawn as nodeSpawn } from "node:child_process";
export interface SpawnOptions {
cmd: string;
args: string[];
env?: Record<string, string>;
input?: string;
timeout?: number;
onStdout?: (chunk: string) => void;
onStderr?: (chunk: string) => void;
}
export interface SpawnResult {
stdout: string;
stderr: string;
exitCode: number;
durationMs: number;
}
/**
* Spawn a subprocess with streaming callbacks and buffered results
*/
export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
const { cmd, args, env, input, timeout, onStdout, onStderr } = options;
const startTime = Date.now();
let stdoutBuffer = "";
let stderrBuffer = "";
return new Promise((resolve, reject) => {
// Spawn the child process
const child = nodeSpawn(cmd, args, {
env: env ? { ...process.env, ...env } : process.env,
stdio: ["pipe", "pipe", "pipe"],
});
// Set up timeout if specified
let timeoutId: NodeJS.Timeout | undefined;
let isTimedOut = false;
if (timeout) {
timeoutId = setTimeout(() => {
isTimedOut = true;
child.kill("SIGTERM");
// If SIGTERM doesn't work, use SIGKILL after 5 seconds
setTimeout(() => {
if (!child.killed) {
child.kill("SIGKILL");
}
}, 5000);
}, timeout);
}
// Handle stdout streaming
if (child.stdout) {
child.stdout.on("data", (data: Buffer) => {
const chunk = data.toString();
stdoutBuffer += chunk;
onStdout?.(chunk);
});
}
// Handle stderr streaming
if (child.stderr) {
child.stderr.on("data", (data: Buffer) => {
const chunk = data.toString();
stderrBuffer += chunk;
onStderr?.(chunk);
});
}
// Handle process completion
child.on("close", (exitCode) => {
const durationMs = Date.now() - startTime;
if (timeoutId) {
clearTimeout(timeoutId);
}
if (isTimedOut) {
reject(new Error(`Process timed out after ${timeout}ms`));
return;
}
resolve({
stdout: stdoutBuffer,
stderr: stderrBuffer,
exitCode: exitCode || 0,
durationMs,
});
});
// Handle process errors
child.on("error", (error) => {
const durationMs = Date.now() - startTime;
if (timeoutId) {
clearTimeout(timeoutId);
}
// Still return buffered output even on error
resolve({
stdout: stdoutBuffer,
stderr: stderrBuffer,
exitCode: 1,
durationMs,
});
});
// Send input if provided
if (input && child.stdin) {
child.stdin.write(input);
child.stdin.end();
}
});
}
+172
View File
@@ -0,0 +1,172 @@
import { table } from "table";
/**
* Print a formatted table with consistent styling
* @param rows - Array of string arrays representing table rows
* @param options - Optional table configuration
*/
export function tableString(
rows: string[][],
options?: {
title?: string;
indent?: string;
drawHorizontalLine?: (lineIndex: number, rowCount: number) => boolean;
}
): string {
const {
title,
indent = "",
drawHorizontalLine = (lineIndex: number, rowCount: number) => {
return lineIndex === 0 || (options?.title && lineIndex === 1) || lineIndex === rowCount;
},
} = options || {};
if (options?.title) {
rows.unshift([options.title]);
}
const tableOutput = table(rows, {
drawHorizontalLine,
border: {
topBody: ``,
topJoin: ``,
topLeft: ``,
topRight: ``,
bottomBody: ``,
bottomJoin: ``,
bottomLeft: ``,
bottomRight: ``,
bodyLeft: ``,
bodyRight: ``,
bodyJoin: ``,
joinBody: ``,
joinLeft: ``,
joinRight: ``,
joinJoin: ``,
},
});
const indentedOutput = tableOutput.split("\n").join(`\n${indent}`).trim();
return `${indent}${indentedOutput}`;
}
/**
* Print a multi-line string in a formatted box with text wrapping
* @param text - The text to display
* @param options - Optional configuration for the box
*/
export function boxString(
text: string,
options?: {
title?: string;
maxWidth?: number;
indent?: string;
padding?: number;
}
): string {
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
// Clean up the text and split into lines
const lines = text.trim().split("\n");
// Word wrap each line to fit within maxWidth
const wrappedLines: string[] = [];
for (const line of lines) {
if (line.length <= maxWidth - padding * 2) {
wrappedLines.push(line);
} else {
// Word wrap the line
const words = line.split(" ");
let currentLine = "";
for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (testLine.length <= maxWidth - padding * 2) {
currentLine = testLine;
} else {
if (currentLine) {
wrappedLines.push(currentLine);
currentLine = word;
} else {
// Word is too long, break it
wrappedLines.push(word.substring(0, maxWidth - padding * 2));
currentLine = word.substring(maxWidth - padding * 2);
}
}
}
if (currentLine) {
wrappedLines.push(currentLine);
}
}
}
// Find the maximum line length for box sizing
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
const boxWidth = maxLineLength + padding * 2;
// Create the box
const topBorder = "┌" + "─".repeat(boxWidth) + "┐";
const bottomBorder = "└" + "─".repeat(boxWidth) + "┘";
const sideBorder = "│";
let result = "";
// Add title if provided
if (title) {
const titleLine = ` ${title} `;
const titlePadding = Math.max(0, boxWidth - titleLine.length);
result += `${indent}${titleLine}${"─".repeat(titlePadding)}\n`;
}
// Add top border (or title border)
if (!title) {
result += `${indent}${topBorder}\n`;
}
// Add content lines
for (const line of wrappedLines) {
const paddedLine = line.padEnd(maxLineLength);
result += `${indent}${sideBorder}${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}${sideBorder}\n`;
}
// Add bottom border
result += `${indent}${bottomBorder}`;
return result;
}
// /**
// * Create a simple two-column table for displaying key-value pairs
// * @param data - Array of [key, value] pairs
// * @param title - Optional table title
// * @param indent - Optional indentation string
// */
// export function printKeyValueTable(
// data: [string, string][],
// title?: string,
// indent?: string
// ): void {
// const rows: string[][] = [["Key", "Value"], ...data];
// const options: Parameters<typeof printTable>[1] = {};
// if (title !== undefined) options.title = title;
// if (indent !== undefined) options.indent = indent;
// printTable(rows, options);
// }
// /**
// * Create a path resolution table (specific use case)
// * @param pathData - Array of [location, resolvedPath] pairs
// * @param indent - Optional indentation string
// */
// export function printPathTable(pathData: [string, string][], indent?: string): void {
// const rows: string[][] = [["Location", "Resolved path"], ...pathData];
// const options: Parameters<typeof printTable>[1] = {};
// if (indent !== undefined) options.indent = indent;
// printTable(rows, options);
// }