Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ea1d95b70 | |||
| 6d0c21f0f5 | |||
| c31824144b | |||
| 0a63f3da9d | |||
| 42b023cc86 | |||
| 854e3d5e4d | |||
| 5bb1b779a8 | |||
| 599264694e | |||
| b9c15e9f38 | |||
| 7ef44eb254 | |||
| 5a21d40d27 | |||
| 175f92542e | |||
| b448787f24 | |||
| 65e3da81e9 | |||
| f31e3a026e | |||
| 220652f27b | |||
| 349af82bfc | |||
| 15732d126d | |||
| 36b006108b | |||
| 029ae0d280 | |||
| 92b435eb80 | |||
| cacf9674c4 | |||
| f73260e3e6 | |||
| 3ddd6db7ca | |||
| 68499340e4 | |||
| acb06634be | |||
| 681e08557c | |||
| 15a7154aea |
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Ensure lockfile is up to date
|
||||||
|
echo "🔒 Updating lockfile..."
|
||||||
|
pnpm install --lockfile-only
|
||||||
|
|
||||||
|
# Build the action before committing
|
||||||
|
echo "🔨 Building action..."
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# Add the built files and lockfile to the commit
|
||||||
|
git add entry.js pnpm-lock.yaml
|
||||||
+2
-26
@@ -12,32 +12,8 @@ inputs:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "node20"
|
||||||
steps:
|
main: "entry.js"
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
- name: Setup Node.js 24
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "24"
|
|
||||||
cache: "pnpm"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
shell: bash
|
|
||||||
working-directory: ${{ github.action_path }}
|
|
||||||
- name: Run agent
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE
|
|
||||||
node ${{ github.action_path }}/entry.ts
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
INPUTS_JSON: ${{ toJSON(inputs) }}
|
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
icon: "code"
|
icon: "code"
|
||||||
|
|||||||
+140
-48
@@ -1,56 +1,113 @@
|
|||||||
import * as core from "@actions/core";
|
import { execSync } from "node:child_process";
|
||||||
|
import { createWriteStream, existsSync, rmSync } from "node:fs";
|
||||||
|
import { mkdtemp } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { pipeline } from "node:stream/promises";
|
||||||
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||||
import { createMcpConfig } from "../mcp/config.ts";
|
import packageJson from "../package.json" with { type: "json" };
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { debugLog, isDebug } from "../utils/logging.ts";
|
import { type Agent, instructions } from "./shared.ts";
|
||||||
import { instructions } from "./shared.ts";
|
|
||||||
import type { Agent, AgentConfig, AgentResult } from "./types.ts";
|
|
||||||
|
|
||||||
/**
|
let cachedCliPath: string | undefined;
|
||||||
* Claude Code agent implementation
|
|
||||||
*/
|
|
||||||
export class ClaudeAgent implements Agent {
|
|
||||||
private apiKey: string;
|
|
||||||
private githubInstallationToken: string;
|
|
||||||
|
|
||||||
constructor(config: AgentConfig) {
|
export const claude: Agent = {
|
||||||
this.apiKey = config.apiKey;
|
install: async (): Promise<string> => {
|
||||||
this.githubInstallationToken = config.githubInstallationToken;
|
if (cachedCliPath) {
|
||||||
}
|
log.info(`Using cached Claude Code CLI at ${cachedCliPath}`);
|
||||||
|
return cachedCliPath;
|
||||||
/**
|
|
||||||
* Install is a no-op since Claude CLI is bundled with the SDK
|
|
||||||
*/
|
|
||||||
async install(): Promise<void> {
|
|
||||||
// No installation needed - CLI is bundled with @anthropic-ai/claude-agent-sdk
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute Claude Code with the given prompt using the SDK
|
|
||||||
*/
|
|
||||||
async execute(prompt: string): Promise<AgentResult> {
|
|
||||||
log.info("Running Claude Agent SDK...");
|
|
||||||
|
|
||||||
log.box(prompt, { title: "Prompt" });
|
|
||||||
|
|
||||||
const mcpConfig = JSON.parse(createMcpConfig(this.githubInstallationToken));
|
|
||||||
|
|
||||||
if (isDebug()) {
|
|
||||||
debugLog(`📋 MCP Config: ${JSON.stringify(mcpConfig, null, 2)}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize session
|
// Get the SDK version from package.json and resolve to actual version
|
||||||
core.info(`🚀 Starting Claude Agent SDK session...`);
|
const versionRange = packageJson.dependencies["@anthropic-ai/claude-agent-sdk"] || "latest";
|
||||||
|
let sdkVersion: string;
|
||||||
|
|
||||||
// Set API key environment variable for SDK
|
// If it's a range (starts with ^ or ~), query npm registry for the latest matching version
|
||||||
process.env.ANTHROPIC_API_KEY = this.apiKey;
|
if (versionRange.startsWith("^") || versionRange.startsWith("~")) {
|
||||||
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
||||||
|
log.info(`Resolving version for range ${versionRange}...`);
|
||||||
|
try {
|
||||||
|
const registryResponse = await fetch(`${npmRegistry}/@anthropic-ai/claude-agent-sdk`);
|
||||||
|
if (!registryResponse.ok) {
|
||||||
|
throw new Error(`Failed to query registry: ${registryResponse.status}`);
|
||||||
|
}
|
||||||
|
const registryData = (await registryResponse.json()) as {
|
||||||
|
"dist-tags": { latest: string };
|
||||||
|
versions: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
// Get the latest version that matches the range (simplified: just use latest)
|
||||||
|
sdkVersion = registryData["dist-tags"].latest;
|
||||||
|
log.info(`Resolved to version ${sdkVersion}`);
|
||||||
|
} catch (error) {
|
||||||
|
log.warning(
|
||||||
|
`Failed to resolve version from registry, using latest: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
);
|
||||||
|
sdkVersion = "latest";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sdkVersion = versionRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`📦 Installing Claude Code CLI from @anthropic-ai/claude-agent-sdk@${sdkVersion}...`);
|
||||||
|
|
||||||
|
// Create temp directory
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), "claude-cli-"));
|
||||||
|
const tarballPath = join(tempDir, "package.tgz");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Download tarball from npm
|
||||||
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
||||||
|
const tarballUrl = `${npmRegistry}/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-${sdkVersion}.tgz`;
|
||||||
|
|
||||||
|
log.info(`Downloading from ${tarballUrl}...`);
|
||||||
|
const response = await fetch(tarballUrl);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write tarball to file
|
||||||
|
const fileStream = createWriteStream(tarballPath);
|
||||||
|
await pipeline(response.body as any, fileStream);
|
||||||
|
log.info(`Downloaded tarball to ${tarballPath}`);
|
||||||
|
|
||||||
|
// Extract tarball
|
||||||
|
log.info(`Extracting tarball...`);
|
||||||
|
execSync(`tar -xzf "${tarballPath}" -C "${tempDir}"`, { stdio: "pipe" });
|
||||||
|
|
||||||
|
// Find cli.js in the extracted package
|
||||||
|
const extractedDir = join(tempDir, "package");
|
||||||
|
const cliPath = join(extractedDir, "cli.js");
|
||||||
|
|
||||||
|
if (!existsSync(cliPath)) {
|
||||||
|
throw new Error(`cli.js not found in extracted package at ${cliPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedCliPath = cliPath;
|
||||||
|
log.info(`✓ Claude Code CLI installed at ${cliPath}`);
|
||||||
|
return cliPath;
|
||||||
|
} catch (error) {
|
||||||
|
// Cleanup on error
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
run: async ({ prompt, mcpServers, apiKey }) => {
|
||||||
|
process.env.ANTHROPIC_API_KEY = apiKey;
|
||||||
|
|
||||||
|
if (!cachedCliPath) {
|
||||||
|
throw new Error("Claude CLI not installed. Call install() before run().");
|
||||||
|
}
|
||||||
|
|
||||||
// Create the query with SDK options
|
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
prompt: `${instructions}\n\n${prompt}`,
|
prompt: `${instructions}\n\n****** USER PROMPT ******\n${prompt}`,
|
||||||
options: {
|
options: {
|
||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
mcpServers: mcpConfig.mcpServers,
|
mcpServers,
|
||||||
|
pathToClaudeCodeExecutable: cachedCliPath,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,14 +117,12 @@ export class ClaudeAgent implements Agent {
|
|||||||
await handler(message as never);
|
await handler(message as never);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.success("Task complete.");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
output: "",
|
output: "",
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
type SDKMessageType = SDKMessage["type"];
|
type SDKMessageType = SDKMessage["type"];
|
||||||
|
|
||||||
@@ -79,6 +134,9 @@ type SDKMessageHandlers = {
|
|||||||
[type in SDKMessageType]: SDKMessageHandler<type>;
|
[type in SDKMessageType]: SDKMessageHandler<type>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Track bash tool IDs to identify when bash tool results come back
|
||||||
|
const bashToolIds = new Set<string>();
|
||||||
|
|
||||||
const messageHandlers: SDKMessageHandlers = {
|
const messageHandlers: SDKMessageHandlers = {
|
||||||
assistant: (data) => {
|
assistant: (data) => {
|
||||||
if (data.message?.content) {
|
if (data.message?.content) {
|
||||||
@@ -88,6 +146,11 @@ const messageHandlers: SDKMessageHandlers = {
|
|||||||
} else if (content.type === "tool_use") {
|
} else if (content.type === "tool_use") {
|
||||||
log.info(`→ ${content.name}`);
|
log.info(`→ ${content.name}`);
|
||||||
|
|
||||||
|
// Track bash tool IDs
|
||||||
|
if (content.name === "bash" && content.id) {
|
||||||
|
bashToolIds.add(content.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (content.input) {
|
if (content.input) {
|
||||||
const input = content.input as any;
|
const input = content.input as any;
|
||||||
if (input.description) log.info(` └─ ${input.description}`);
|
if (input.description) log.info(` └─ ${input.description}`);
|
||||||
@@ -119,8 +182,35 @@ const messageHandlers: SDKMessageHandlers = {
|
|||||||
user: (data) => {
|
user: (data) => {
|
||||||
if (data.message?.content) {
|
if (data.message?.content) {
|
||||||
for (const content of data.message.content) {
|
for (const content of data.message.content) {
|
||||||
if (content.type === "tool_result" && content.is_error) {
|
if (content.type === "tool_result") {
|
||||||
log.warning(`Tool error: ${content.content}`);
|
const toolUseId = (content as any).tool_use_id;
|
||||||
|
const isBashTool = toolUseId && bashToolIds.has(toolUseId);
|
||||||
|
|
||||||
|
if (isBashTool) {
|
||||||
|
// Log bash output in a collapsed group
|
||||||
|
const outputContent =
|
||||||
|
typeof content.content === "string"
|
||||||
|
? content.content
|
||||||
|
: Array.isArray(content.content)
|
||||||
|
? content.content
|
||||||
|
.map((c: any) => (typeof c === "string" ? c : c.text || JSON.stringify(c)))
|
||||||
|
.join("\n")
|
||||||
|
: String(content.content);
|
||||||
|
|
||||||
|
log.startGroup(`bash output`);
|
||||||
|
if (content.is_error) {
|
||||||
|
log.warning(outputContent);
|
||||||
|
} else {
|
||||||
|
log.info(outputContent);
|
||||||
|
}
|
||||||
|
log.endGroup();
|
||||||
|
// Clean up the tracked ID
|
||||||
|
bashToolIds.delete(toolUseId);
|
||||||
|
} else if (content.is_error) {
|
||||||
|
const errorContent =
|
||||||
|
typeof content.content === "string" ? content.content : String(content.content);
|
||||||
|
log.warning(`Tool error: ${errorContent}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,4 +239,6 @@ const messageHandlers: SDKMessageHandlers = {
|
|||||||
},
|
},
|
||||||
system: () => {},
|
system: () => {},
|
||||||
stream_event: () => {},
|
stream_event: () => {},
|
||||||
|
tool_progress: () => {},
|
||||||
|
auth_status: () => {},
|
||||||
};
|
};
|
||||||
|
|||||||
+60
-11
@@ -1,15 +1,64 @@
|
|||||||
import { mcpServerName } from "../mcp/config.ts";
|
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||||
|
import { ghPullfrogMcpName } from "../mcp/config.ts";
|
||||||
|
|
||||||
export const instructions = `- use the ${mcpServerName} MCP server to interact with github
|
/**
|
||||||
- if ${mcpServerName} is not available or doesn't include the functionality you need, describe why and bail
|
* Result returned by agent execution
|
||||||
- do not under any circumstances use the gh cli
|
*/
|
||||||
- if prompted by a comment to respond to create a new issue, pr or anything else, after succeeding,
|
export interface AgentResult {
|
||||||
also respond to the original comment with a very brief message containing a link to it
|
success: boolean;
|
||||||
|
output?: string;
|
||||||
|
error?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for agent creation
|
||||||
|
*/
|
||||||
|
export interface AgentConfig {
|
||||||
|
apiKey: string;
|
||||||
|
githubInstallationToken: string;
|
||||||
|
prompt: string;
|
||||||
|
mcpServers: Record<string, McpServerConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Agent = {
|
||||||
|
install: () => Promise<string>;
|
||||||
|
run: (config: AgentConfig) => Promise<AgentResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const instructions = `
|
||||||
|
You are a highly intelligent, no-nonsense senior-level software engineering agent. You are careful, to-the-point, and kind. You only say things you know to be true. Your code is focused, minimal, and production-ready. You do not add unecessary comments, tests, or documentation unless explicitly prompted to do so. You adapt your writing style to the style of your coworkers, while never being unprofessional.
|
||||||
|
|
||||||
|
- eagerly inspect your MCP servers to determine what tools are available to you, especially ${ghPullfrogMcpName}
|
||||||
|
- do not under any circumstances use the github cli (\`gh\`). find the corresponding tool from ${ghPullfrogMcpName} instead.
|
||||||
|
- mode selection: choose the appropriate mode based on the prompt payload:
|
||||||
|
- choose "plan mode" if the prompt asks to:
|
||||||
|
- create a plan, break down tasks, outline steps, or analyze requirements
|
||||||
|
- understand the scope of work before implementation
|
||||||
|
- provide a todo list or task breakdown
|
||||||
|
- choose "implement" if the prompt asks to:
|
||||||
|
- implement, build, create, or develop code changes
|
||||||
|
- make specific changes to files or features
|
||||||
|
- execute a plan that was previously created
|
||||||
|
- the prompt includes specific implementation details or requirements
|
||||||
|
- choose "review" if the prompt asks to:
|
||||||
|
- review code, PR, or implementation
|
||||||
|
- provide feedback, suggestions, or identify issues
|
||||||
|
- check code quality, style, or correctness
|
||||||
|
- once you've chosen a mode, follow its associated prompts carefully
|
||||||
|
- when prompted directly (e.g., via issue comment or PR comment):
|
||||||
|
(1) start by creating a single response comment using mcp__${ghPullfrogMcpName}__create_issue_comment
|
||||||
|
- the initial comment should say something like "I'll do {summary of request}" where you summarize what was requested
|
||||||
|
- save the commentId returned from this initial comment creation
|
||||||
|
(2) use mcp__${ghPullfrogMcpName}__edit_issue_comment to progressively update that same comment as you make progress
|
||||||
|
- update the comment with current status, completed tasks, and any relevant information
|
||||||
|
- continue updating the same comment throughout the planning/implementation process
|
||||||
|
(3) create_issue_comment should only be used once initially - all subsequent updates must use edit_issue_comment with the saved commentId
|
||||||
- if prompted to review a PR:
|
- if prompted to review a PR:
|
||||||
(1) get PR info with mcp__${mcpServerName}__get_pull_request
|
(1) get PR info with mcp__${ghPullfrogMcpName}__get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||||
(2) fetch both branches: git fetch origin <base> --depth=20 && git fetch origin <head>
|
(2) view diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments)
|
||||||
(3) checkout the PR branch: git checkout origin/<head> (you MUST do this before reading any files)
|
(3) read files from the checked-out PR branch to understand the implementation
|
||||||
(4) view diff: git diff origin/<base>...origin/<head> (this shows what changed)
|
(4) when submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||||
(5) read files from the checked-out PR branch to understand the implementation
|
(5) only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location
|
||||||
replace <base> and <head> with 'base' and 'head' from the PR info
|
replace <base> and <head> with 'base' and 'head' from the PR info
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
githubInstallationToken: string;
|
|
||||||
}
|
|
||||||
@@ -4,23 +4,65 @@
|
|||||||
* Entry point for GitHub Action
|
* Entry point for GitHub Action
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { type } from "arktype";
|
import { dirName } from "@ark/fs";
|
||||||
import { Inputs, main } from "./main.ts";
|
import { type Inputs, main } from "./main.ts";
|
||||||
import packageJson from "./package.json" with { type: "json" };
|
import { createMcpServer } from "./mcp/server.ts";
|
||||||
import { log } from "./utils/cli.ts";
|
import { log } from "./utils/cli.ts";
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
// Export createMcpServer so it can be called from the spawned MCP process
|
||||||
try {
|
export { createMcpServer };
|
||||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
|
||||||
|
|
||||||
const inputsJson = process.env.INPUTS_JSON;
|
async function printDirectoryTree(dir: string, prefix = "", rootDir = dir): Promise<string> {
|
||||||
if (!inputsJson) {
|
const entries = await readdir(dir, { withFileTypes: true });
|
||||||
throw new Error("INPUTS_JSON environment variable not found");
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
const entry = entries[i];
|
||||||
|
const isLast = i === entries.length - 1;
|
||||||
|
const currentPrefix = isLast ? "└── " : "├── ";
|
||||||
|
const nextPrefix = isLast ? " " : "│ ";
|
||||||
|
|
||||||
|
const fullPath = join(dir, entry.name);
|
||||||
|
lines.push(`${prefix}${currentPrefix}${entry.name}`);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const subTree = await printDirectoryTree(fullPath, `${prefix}${nextPrefix}`, rootDir);
|
||||||
|
lines.push(subTree);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const parsed = type("string.json.parse").assert(inputsJson);
|
return lines.join("\n");
|
||||||
const inputs = Inputs.assert(parsed);
|
}
|
||||||
|
|
||||||
|
async function run(): Promise<void> {
|
||||||
|
// Debug: Print current directory tree before changing directories
|
||||||
|
const actionDir = dirName();
|
||||||
|
log.info(`action dir: ${actionDir}`);
|
||||||
|
try {
|
||||||
|
const tree = await printDirectoryTree(actionDir);
|
||||||
|
log.info(`Directory tree:\n${tree}`);
|
||||||
|
} catch (error) {
|
||||||
|
log.warning(
|
||||||
|
`Failed to print directory tree: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to GITHUB_WORKSPACE if set (this is where actions/checkout puts the repo)
|
||||||
|
// JavaScript actions run from the action's directory, not the checked-out repo
|
||||||
|
if (process.env.GITHUB_WORKSPACE && process.cwd() !== process.env.GITHUB_WORKSPACE) {
|
||||||
|
log.debug(`Changing to GITHUB_WORKSPACE: ${process.env.GITHUB_WORKSPACE}`);
|
||||||
|
process.chdir(process.env.GITHUB_WORKSPACE);
|
||||||
|
log.debug(`New working directory: ${process.cwd()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const inputs: Inputs = {
|
||||||
|
prompt: core.getInput("prompt", { required: true }),
|
||||||
|
anthropic_api_key: core.getInput("anthropic_api_key") || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const result = await main(inputs);
|
const result = await main(inputs);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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.js",
|
||||||
|
format: "esm",
|
||||||
|
platform: "node",
|
||||||
|
target: "node20",
|
||||||
|
minify: false,
|
||||||
|
sourcemap: false,
|
||||||
|
// Bundle all dependencies - GitHub Actions doesn't have node_modules
|
||||||
|
// Only mark optional peer dependencies as external
|
||||||
|
external: [
|
||||||
|
"@valibot/to-json-schema",
|
||||||
|
"effect",
|
||||||
|
"sury",
|
||||||
|
],
|
||||||
|
// Provide a proper require shim for CommonJS modules bundled into ESM
|
||||||
|
// We use a unique variable name to avoid conflicts with bundled imports
|
||||||
|
banner: {
|
||||||
|
js: `import { createRequire as __createRequire } from 'module'; import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __dirnameFn } from 'path'; const require = __createRequire(import.meta.url); const __filename = __fileURLToPath(import.meta.url); const __dirname = __dirnameFn(__filename);`,
|
||||||
|
},
|
||||||
|
// Enable tree-shaking to remove unused code
|
||||||
|
treeShaking: true,
|
||||||
|
// Drop console statements in production (but keep for debugging)
|
||||||
|
drop: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("✅ Build completed successfully!");
|
||||||
|
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
add a github review for the following PR: https://github.com/pullfrogai/scratch/pull/16
|
run npx cowsay "don't eat me"
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
* This exports the main function for programmatic usage
|
* This exports the main function for programmatic usage
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { ClaudeAgent } from "./agents/claude.ts";
|
export type { Agent, AgentConfig, AgentResult } from "./agents/shared.ts";
|
||||||
export type { Agent, AgentConfig, AgentResult } from "./agents/types.ts";
|
|
||||||
export {
|
export {
|
||||||
type Inputs as ExecutionInputs,
|
type Inputs as ExecutionInputs,
|
||||||
type MainResult,
|
type MainResult,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { ClaudeAgent } from "./agents/claude.ts";
|
import { claude } from "./agents/claude.ts";
|
||||||
|
import { createMcpConfigs } from "./mcp/config.ts";
|
||||||
|
import packageJson from "./package.json" with { type: "json" };
|
||||||
|
import { log } from "./utils/cli.ts";
|
||||||
import { parseRepoContext, setupGitHubInstallationToken } from "./utils/github.ts";
|
import { parseRepoContext, setupGitHubInstallationToken } from "./utils/github.ts";
|
||||||
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
|
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
|
||||||
|
|
||||||
@@ -17,9 +19,11 @@ export interface MainResult {
|
|||||||
error?: string | undefined;
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PromptJSON = {};
|
||||||
|
|
||||||
export async function main(inputs: Inputs): Promise<MainResult> {
|
export async function main(inputs: Inputs): Promise<MainResult> {
|
||||||
try {
|
try {
|
||||||
core.info(`→ Starting agent run with Claude Code`);
|
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||||
|
|
||||||
setupGitConfig();
|
setupGitConfig();
|
||||||
|
|
||||||
@@ -28,15 +32,27 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
|||||||
|
|
||||||
setupGitAuth(githubInstallationToken, repoContext);
|
setupGitAuth(githubInstallationToken, repoContext);
|
||||||
|
|
||||||
const agent = new ClaudeAgent({
|
const mcpServers = createMcpConfigs(githubInstallationToken);
|
||||||
apiKey: inputs.anthropic_api_key!,
|
|
||||||
|
log.debug(`📋 MCP Config: ${JSON.stringify(mcpServers, null, 2)}`);
|
||||||
|
|
||||||
|
// Install Claude CLI before running
|
||||||
|
await claude.install();
|
||||||
|
|
||||||
|
log.info("Running Claude Agent SDK...");
|
||||||
|
log.box(inputs.prompt, { title: "Prompt" });
|
||||||
|
|
||||||
|
// TODO: check if `inputs.prompts` is JSON
|
||||||
|
// if yes, check if it's a webhook payload or toJSON(github.event)
|
||||||
|
// for webhook payloads, check the specified `agent` field
|
||||||
|
|
||||||
|
const result = await claude.run({
|
||||||
|
prompt: inputs.prompt,
|
||||||
|
mcpServers,
|
||||||
githubInstallationToken,
|
githubInstallationToken,
|
||||||
|
apiKey: inputs.anthropic_api_key!,
|
||||||
});
|
});
|
||||||
|
|
||||||
await agent.install();
|
|
||||||
|
|
||||||
const result = await agent.execute(inputs.prompt);
|
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -45,12 +61,17 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.success("Task complete.");
|
||||||
|
await log.writeSummary();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
output: result.output || "",
|
output: result.output || "",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||||
|
log.error(errorMessage);
|
||||||
|
await log.writeSummary();
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
|
|||||||
+28
-1
@@ -6,7 +6,7 @@ export const Comment = type({
|
|||||||
body: type.string.describe("the comment body content"),
|
body: type.string.describe("the comment body content"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CommentTool = tool({
|
export const CreateCommentTool = tool({
|
||||||
name: "create_issue_comment",
|
name: "create_issue_comment",
|
||||||
description: "Create a comment on a GitHub issue",
|
description: "Create a comment on a GitHub issue",
|
||||||
parameters: Comment,
|
parameters: Comment,
|
||||||
@@ -26,3 +26,30 @@ export const CommentTool = tool({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const EditComment = type({
|
||||||
|
commentId: type.number.describe("the ID of the comment to edit"),
|
||||||
|
body: type.string.describe("the new comment body content"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const EditCommentTool = tool({
|
||||||
|
name: "edit_issue_comment",
|
||||||
|
description: "Edit a GitHub issue comment by its ID",
|
||||||
|
parameters: EditComment,
|
||||||
|
execute: contextualize(async ({ commentId, body }, ctx) => {
|
||||||
|
const result = await ctx.octokit.rest.issues.updateComment({
|
||||||
|
owner: ctx.owner,
|
||||||
|
repo: ctx.name,
|
||||||
|
comment_id: commentId,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
commentId: result.data.id,
|
||||||
|
url: result.data.html_url,
|
||||||
|
body: result.data.body,
|
||||||
|
updatedAt: result.data.updated_at,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
+24
-19
@@ -1,32 +1,37 @@
|
|||||||
/**
|
/**
|
||||||
* Simple MCP configuration helper for adding our minimal GitHub comment server
|
* Simple MCP configuration helper for adding our minimal GitHub comment server
|
||||||
*/
|
*/
|
||||||
import { fromHere } from "@ark/fs";
|
|
||||||
|
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||||
import { parseRepoContext } from "../utils/github.ts";
|
import { parseRepoContext } from "../utils/github.ts";
|
||||||
|
|
||||||
const actionPath = fromHere("..");
|
export const ghPullfrogMcpName = "gh-pullfrog";
|
||||||
|
|
||||||
export const mcpServerName = "gh-pullfrog";
|
export type McpName = typeof ghPullfrogMcpName;
|
||||||
|
|
||||||
export function createMcpConfig(githubInstallationToken: string) {
|
export type McpConfigs = Record<McpName, McpServerConfig>;
|
||||||
|
|
||||||
|
export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
|
||||||
const repoContext = parseRepoContext();
|
const repoContext = parseRepoContext();
|
||||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||||
|
|
||||||
return JSON.stringify(
|
// Get absolute path to entry.js - use GITHUB_ACTION_PATH if available, otherwise current directory
|
||||||
{
|
const entryPath = process.env.GITHUB_ACTION_PATH
|
||||||
mcpServers: {
|
? `${process.env.GITHUB_ACTION_PATH}/entry.js`
|
||||||
[mcpServerName]: {
|
: `${process.cwd()}/entry.js`;
|
||||||
command: "node",
|
|
||||||
args: [`${actionPath}/mcp/server.ts`],
|
return {
|
||||||
env: {
|
[ghPullfrogMcpName]: {
|
||||||
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
command: "node",
|
||||||
GITHUB_REPOSITORY: githubRepository,
|
args: [
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
"--input-type=module",
|
||||||
},
|
"-e",
|
||||||
},
|
`import('${entryPath.replace(/'/g, "\\'")}').then(m => m.createMcpServer())`,
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
||||||
|
GITHUB_REPOSITORY: githubRepository,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
};
|
||||||
2
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
|
import { log } from "../utils/cli.ts";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
export const PullRequest = type({
|
export const PullRequest = type({
|
||||||
@@ -18,7 +19,7 @@ export const PullRequestTool = tool({
|
|||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
console.log(`Current branch: ${currentBranch}`);
|
log.info(`Current branch: ${currentBranch}`);
|
||||||
|
|
||||||
const result = await ctx.octokit.rest.pulls.create({
|
const result = await ctx.octokit.rest.pulls.create({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
|
|||||||
+19
-2
@@ -1,4 +1,6 @@
|
|||||||
|
import { execSync } from "node:child_process";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
|
import { log } from "../utils/cli.ts";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
export const PullRequestInfo = type({
|
export const PullRequestInfo = type({
|
||||||
@@ -7,7 +9,8 @@ export const PullRequestInfo = type({
|
|||||||
|
|
||||||
export const PullRequestInfoTool = tool({
|
export const PullRequestInfoTool = tool({
|
||||||
name: "get_pull_request",
|
name: "get_pull_request",
|
||||||
description: "Retrieve minimal information for a specific pull request by number.",
|
description:
|
||||||
|
"Retrieve PR information and automatically prepare the repository for review by fetching and checking out the PR branch.",
|
||||||
parameters: PullRequestInfo,
|
parameters: PullRequestInfo,
|
||||||
execute: contextualize(async ({ pull_number }, ctx) => {
|
execute: contextualize(async ({ pull_number }, ctx) => {
|
||||||
const pr = await ctx.octokit.rest.pulls.get({
|
const pr = await ctx.octokit.rest.pulls.get({
|
||||||
@@ -17,10 +20,24 @@ export const PullRequestInfoTool = tool({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = pr.data;
|
const data = pr.data;
|
||||||
|
|
||||||
const baseBranch = data.base.ref;
|
const baseBranch = data.base.ref;
|
||||||
const headBranch = data.head.ref;
|
const headBranch = data.head.ref;
|
||||||
|
|
||||||
|
if (!baseBranch) {
|
||||||
|
throw new Error(`Base branch not found for PR #${pull_number}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically fetch and checkout branches for review
|
||||||
|
log.info(`Fetching base branch: origin/${baseBranch}`);
|
||||||
|
execSync(`git fetch origin ${baseBranch} --depth=20`, { stdio: "inherit" });
|
||||||
|
|
||||||
|
log.info(`Fetching PR branch: origin/${headBranch}`);
|
||||||
|
execSync(`git fetch origin ${headBranch}`, { stdio: "inherit" });
|
||||||
|
|
||||||
|
log.info(`Checking out PR branch: origin/${headBranch}`);
|
||||||
|
execSync(`git checkout origin/${headBranch}`, { stdio: "inherit" });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
number: data.number,
|
number: data.number,
|
||||||
url: data.html_url,
|
url: data.html_url,
|
||||||
|
|||||||
+64
-16
@@ -1,28 +1,58 @@
|
|||||||
|
import type { RestEndpointMethodTypes } from "@octokit/rest";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
import type { RestEndpointMethodTypes } from "@octokit/rest";
|
|
||||||
|
|
||||||
export const Review = type({
|
export const Review = type({
|
||||||
pull_number: type.number.describe("The pull request number to review"),
|
pull_number: type.number.describe("The pull request number to review"),
|
||||||
event: type.enumerated("APPROVE", "REQUEST_CHANGES", "COMMENT").describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"),
|
event: type
|
||||||
body: type.string.describe("The body content for the review (required for REQUEST_CHANGES or COMMENT)").optional(),
|
.enumerated("APPROVE", "REQUEST_CHANGES", "COMMENT")
|
||||||
commit_id: type.string.describe("Optional SHA of the commit being reviewed. Defaults to latest.").optional(),
|
.describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"),
|
||||||
comments: type
|
body: type.string
|
||||||
({
|
.describe(
|
||||||
path: type.string.describe("The file path to comment on"),
|
"Brief summary or general feedback that doesn't apply to specific code locations. Keep it concise - most feedback should be in the 'comments' array."
|
||||||
position: type.number.describe("The diff position in the file"),
|
)
|
||||||
body: type.string.describe("The comment text"),
|
.optional(),
|
||||||
})
|
commit_id: type.string
|
||||||
.array()
|
.describe("Optional SHA of the commit being reviewed. Defaults to latest.")
|
||||||
.describe("Array of draft review comments for specific lines, optional.")
|
.optional(),
|
||||||
.optional()
|
comments: type({
|
||||||
|
path: type.string.describe("The file path to comment on (relative to repo root)"),
|
||||||
|
line: type.number.describe(
|
||||||
|
"The line number in the file (use line numbers from the diff - usually the RIGHT side/new code)"
|
||||||
|
),
|
||||||
|
side: type
|
||||||
|
.enumerated("LEFT", "RIGHT")
|
||||||
|
.describe(
|
||||||
|
"Side of the diff: LEFT (old code) or RIGHT (new code). Defaults to RIGHT if not provided."
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
body: type.string.describe("The comment text for this specific line"),
|
||||||
|
start_line: type.number
|
||||||
|
.describe("Start line for multi-line comments (optional, for commenting on ranges)")
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.describe(
|
||||||
|
"REQUIRED: Array of inline comments for specific code issues. Use this for all location-specific feedback. Use 'git diff origin/<base>...origin/<head>' to find the correct line numbers (typically use the line numbers shown on the RIGHT side for new code, LEFT side for old code)."
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ReviewTool = tool({
|
export const ReviewTool = tool({
|
||||||
name: "submit_pull_request_review",
|
name: "submit_pull_request_review",
|
||||||
description: "Submit a review (approve, request changes, or comment) for an existing pull request.",
|
description:
|
||||||
|
"Submit a review (approve, request changes, or comment) for an existing pull request. " +
|
||||||
|
"IMPORTANT: Use 'comments' array for ALL specific code issues at the line-level. " +
|
||||||
|
"Only use 'body' for a brief summary or feedback that doesn't apply to a specific location.",
|
||||||
parameters: Review,
|
parameters: Review,
|
||||||
execute: contextualize(async ({ pull_number, event, body, commit_id, comments = [] }, ctx) => {
|
execute: contextualize(async ({ pull_number, event, body, commit_id, comments = [] }, ctx) => {
|
||||||
|
// Get the PR to determine the head commit if commit_id not provided
|
||||||
|
const pr = await ctx.octokit.rest.pulls.get({
|
||||||
|
owner: ctx.owner,
|
||||||
|
repo: ctx.name,
|
||||||
|
pull_number,
|
||||||
|
});
|
||||||
|
|
||||||
// Compose the request
|
// Compose the request
|
||||||
const params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"] = {
|
const params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"] = {
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
@@ -31,8 +61,26 @@ export const ReviewTool = tool({
|
|||||||
event,
|
event,
|
||||||
};
|
};
|
||||||
if (body) params.body = body;
|
if (body) params.body = body;
|
||||||
if (commit_id) params.commit_id = commit_id;
|
if (commit_id) {
|
||||||
if (comments.length > 0) params.comments = comments;
|
params.commit_id = commit_id;
|
||||||
|
} else {
|
||||||
|
params.commit_id = pr.data.head.sha;
|
||||||
|
}
|
||||||
|
if (comments.length > 0) {
|
||||||
|
type ReviewComment = (typeof params.comments & {})[number];
|
||||||
|
// Convert comments to the format expected by GitHub API
|
||||||
|
params.comments = comments.map((comment) => {
|
||||||
|
const reviewComment: ReviewComment = {
|
||||||
|
...comment,
|
||||||
|
};
|
||||||
|
reviewComment.side = comment.side || "RIGHT";
|
||||||
|
if (comment.start_line) {
|
||||||
|
reviewComment.start_line = comment.start_line;
|
||||||
|
reviewComment.start_side = comment.side || "RIGHT";
|
||||||
|
}
|
||||||
|
return reviewComment;
|
||||||
|
});
|
||||||
|
}
|
||||||
const result = await ctx.octokit.rest.pulls.createReview(params);
|
const result = await ctx.octokit.rest.pulls.createReview(params);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
+17
-8
@@ -1,18 +1,27 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// Minimal GitHub Issue Comment MCP Server
|
// Minimal GitHub Issue Comment MCP Server
|
||||||
import { FastMCP } from "fastmcp";
|
import { FastMCP } from "fastmcp";
|
||||||
import { CommentTool } from "./comment.ts";
|
import { CreateCommentTool, EditCommentTool } from "./comment.ts";
|
||||||
import { IssueTool } from "./issue.ts";
|
import { IssueTool } from "./issue.ts";
|
||||||
import { PullRequestTool } from "./pr.ts";
|
import { PullRequestTool } from "./pr.ts";
|
||||||
import { ReviewTool } from "./review.ts";
|
|
||||||
import { PullRequestInfoTool } from "./prInfo.ts";
|
import { PullRequestInfoTool } from "./prInfo.ts";
|
||||||
|
import { ReviewTool } from "./review.ts";
|
||||||
import { addTools } from "./shared.ts";
|
import { addTools } from "./shared.ts";
|
||||||
|
|
||||||
const server = new FastMCP({
|
export function createMcpServer(): void {
|
||||||
name: "gh-pullfrog",
|
const server = new FastMCP({
|
||||||
version: "0.0.1",
|
name: "gh-pullfrog",
|
||||||
});
|
version: "0.0.1",
|
||||||
|
});
|
||||||
|
|
||||||
addTools(server, [CommentTool, IssueTool, PullRequestTool, ReviewTool, PullRequestInfoTool]);
|
addTools(server, [
|
||||||
|
CreateCommentTool,
|
||||||
|
EditCommentTool,
|
||||||
|
IssueTool,
|
||||||
|
PullRequestTool,
|
||||||
|
ReviewTool,
|
||||||
|
PullRequestInfoTool,
|
||||||
|
]);
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
|
}
|
||||||
|
|||||||
+9
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.65",
|
"version": "0.0.84",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -15,19 +15,21 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "node esbuild.config.js",
|
||||||
"play": "node play.ts",
|
"play": "node play.ts",
|
||||||
"upDeps": "pnpm up --latest",
|
"upDeps": "pnpm up --latest",
|
||||||
"lock": "pnpm --ignore-workspace install"
|
"lock": "pnpm --ignore-workspace install",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.1.26",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.26",
|
||||||
"@ark/fs": "0.50.0",
|
"@ark/fs": "0.53.0",
|
||||||
"@ark/util": "0.50.0",
|
"@ark/util": "0.53.0",
|
||||||
"@octokit/rest": "^22.0.0",
|
"@octokit/rest": "^22.0.0",
|
||||||
"@octokit/webhooks-types": "^7.6.1",
|
"@octokit/webhooks-types": "^7.6.1",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"arktype": "^2.1.25",
|
"arktype": "2.1.25",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"fastmcp": "^3.20.0",
|
"fastmcp": "^3.20.0",
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.7.2",
|
"@types/node": "^24.7.2",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
"esbuild": "^0.25.9",
|
||||||
|
"husky": "^9.0.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { fromHere } from "@ark/fs";
|
|||||||
import arg from "arg";
|
import arg from "arg";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
import { type Inputs, main } from "./main.ts";
|
import { type Inputs, main } from "./main.ts";
|
||||||
import packageJson from "./package.json" with { type: "json" };
|
|
||||||
import { log } from "./utils/cli.ts";
|
import { log } from "./utils/cli.ts";
|
||||||
import { setupTestRepo } from "./utils/setup.ts";
|
import { setupTestRepo } from "./utils/setup.ts";
|
||||||
|
|
||||||
@@ -15,8 +14,6 @@ export async function run(
|
|||||||
prompt: string
|
prompt: string
|
||||||
): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> {
|
): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> {
|
||||||
try {
|
try {
|
||||||
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
|
||||||
|
|
||||||
const tempDir = join(process.cwd(), ".temp");
|
const tempDir = join(process.cwd(), ".temp");
|
||||||
setupTestRepo({ tempDir, forceClean: true });
|
setupTestRepo({ tempDir, forceClean: true });
|
||||||
|
|
||||||
@@ -59,7 +56,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (args["--help"]) {
|
if (args["--help"]) {
|
||||||
console.log(`
|
log.info(`
|
||||||
Usage: tsx play.ts [file] [options]
|
Usage: tsx play.ts [file] [options]
|
||||||
|
|
||||||
Test the Pullfrog action with various prompts.
|
Test the Pullfrog action with various prompts.
|
||||||
|
|||||||
Generated
+289
-13
@@ -15,11 +15,11 @@ importers:
|
|||||||
specifier: ^0.1.26
|
specifier: ^0.1.26
|
||||||
version: 0.1.30(zod@3.25.76)
|
version: 0.1.30(zod@3.25.76)
|
||||||
'@ark/fs':
|
'@ark/fs':
|
||||||
specifier: 0.50.0
|
specifier: 0.53.0
|
||||||
version: 0.50.0
|
version: 0.53.0
|
||||||
'@ark/util':
|
'@ark/util':
|
||||||
specifier: 0.50.0
|
specifier: 0.53.0
|
||||||
version: 0.50.0
|
version: 0.53.0
|
||||||
'@octokit/rest':
|
'@octokit/rest':
|
||||||
specifier: ^22.0.0
|
specifier: ^22.0.0
|
||||||
version: 22.0.0
|
version: 22.0.0
|
||||||
@@ -30,7 +30,7 @@ importers:
|
|||||||
specifier: 1.0.0
|
specifier: 1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
arktype:
|
arktype:
|
||||||
specifier: ^2.1.25
|
specifier: 2.1.25
|
||||||
version: 2.1.25
|
version: 2.1.25
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.2.3
|
specifier: ^17.2.3
|
||||||
@@ -51,6 +51,12 @@ importers:
|
|||||||
arg:
|
arg:
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.25.9
|
||||||
|
version: 0.25.12
|
||||||
|
husky:
|
||||||
|
specifier: ^9.0.0
|
||||||
|
version: 9.1.7
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -75,21 +81,174 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.24.1
|
zod: ^3.24.1
|
||||||
|
|
||||||
'@ark/fs@0.50.0':
|
'@ark/fs@0.53.0':
|
||||||
resolution: {integrity: sha512-6OrxNt2T+/pL4RUMZK/aiVRLIS3acNs5uSpHDyZAP6+OXwXchFSQ1lJTH+uuGBlDWeQxPD7VmymYXLF5s1Eyhw==}
|
resolution: {integrity: sha512-XL0EbBAZgyy+j9aPhftYaBsbKAW5PTNSKCN6oLRRdrHuHPSAZgR6765/z0YZGhPxHEUNmq0vBoSk8yOLk91dNQ==}
|
||||||
|
|
||||||
'@ark/schema@0.53.0':
|
'@ark/schema@0.53.0':
|
||||||
resolution: {integrity: sha512-1PB7RThUiTlmIu8jbSurPrhHpVixPd4C+xNBUF/HrjIENCeDcAMg36n5mpMzED7OQGDVIzpfXXiMnaTiutjHJw==}
|
resolution: {integrity: sha512-1PB7RThUiTlmIu8jbSurPrhHpVixPd4C+xNBUF/HrjIENCeDcAMg36n5mpMzED7OQGDVIzpfXXiMnaTiutjHJw==}
|
||||||
|
|
||||||
'@ark/util@0.50.0':
|
|
||||||
resolution: {integrity: sha512-tIkgIMVRpkfXRQIEf0G2CJryZVtHVrqcWHMDa5QKo0OEEBu0tHkRSIMm4Ln8cd8Bn9TPZtvc/kE2Gma8RESPSg==}
|
|
||||||
|
|
||||||
'@ark/util@0.53.0':
|
'@ark/util@0.53.0':
|
||||||
resolution: {integrity: sha512-TGn4gLlA6dJcQiqrtCtd88JhGb2XBHo6qIejsDre+nxpGuUVW4G3YZGVrwjNBTO0EyR+ykzIo4joHJzOj+/cpA==}
|
resolution: {integrity: sha512-TGn4gLlA6dJcQiqrtCtd88JhGb2XBHo6qIejsDre+nxpGuUVW4G3YZGVrwjNBTO0EyR+ykzIo4joHJzOj+/cpA==}
|
||||||
|
|
||||||
'@borewit/text-codec@0.1.1':
|
'@borewit/text-codec@0.1.1':
|
||||||
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1':
|
'@fastify/busboy@2.1.1':
|
||||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -373,6 +532,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -492,6 +656,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
husky@9.1.7:
|
||||||
|
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -879,18 +1048,94 @@ snapshots:
|
|||||||
'@img/sharp-linux-x64': 0.33.5
|
'@img/sharp-linux-x64': 0.33.5
|
||||||
'@img/sharp-win32-x64': 0.33.5
|
'@img/sharp-win32-x64': 0.33.5
|
||||||
|
|
||||||
'@ark/fs@0.50.0': {}
|
'@ark/fs@0.53.0': {}
|
||||||
|
|
||||||
'@ark/schema@0.53.0':
|
'@ark/schema@0.53.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ark/util': 0.53.0
|
'@ark/util': 0.53.0
|
||||||
|
|
||||||
'@ark/util@0.50.0': {}
|
|
||||||
|
|
||||||
'@ark/util@0.53.0': {}
|
'@ark/util@0.53.0': {}
|
||||||
|
|
||||||
'@borewit/text-codec@0.1.1': {}
|
'@borewit/text-codec@0.1.1': {}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1': {}
|
'@fastify/busboy@2.1.1': {}
|
||||||
|
|
||||||
'@img/sharp-darwin-arm64@0.33.5':
|
'@img/sharp-darwin-arm64@0.33.5':
|
||||||
@@ -1171,6 +1416,35 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.25.12
|
||||||
|
'@esbuild/android-arm': 0.25.12
|
||||||
|
'@esbuild/android-arm64': 0.25.12
|
||||||
|
'@esbuild/android-x64': 0.25.12
|
||||||
|
'@esbuild/darwin-arm64': 0.25.12
|
||||||
|
'@esbuild/darwin-x64': 0.25.12
|
||||||
|
'@esbuild/freebsd-arm64': 0.25.12
|
||||||
|
'@esbuild/freebsd-x64': 0.25.12
|
||||||
|
'@esbuild/linux-arm': 0.25.12
|
||||||
|
'@esbuild/linux-arm64': 0.25.12
|
||||||
|
'@esbuild/linux-ia32': 0.25.12
|
||||||
|
'@esbuild/linux-loong64': 0.25.12
|
||||||
|
'@esbuild/linux-mips64el': 0.25.12
|
||||||
|
'@esbuild/linux-ppc64': 0.25.12
|
||||||
|
'@esbuild/linux-riscv64': 0.25.12
|
||||||
|
'@esbuild/linux-s390x': 0.25.12
|
||||||
|
'@esbuild/linux-x64': 0.25.12
|
||||||
|
'@esbuild/netbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/netbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/openbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openharmony-arm64': 0.25.12
|
||||||
|
'@esbuild/sunos-x64': 0.25.12
|
||||||
|
'@esbuild/win32-arm64': 0.25.12
|
||||||
|
'@esbuild/win32-ia32': 0.25.12
|
||||||
|
'@esbuild/win32-x64': 0.25.12
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
@@ -1343,6 +1617,8 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@8.0.1: {}
|
human-signals@8.0.1: {}
|
||||||
|
|
||||||
|
husky@9.1.7: {}
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[x] add modes to prompt
|
||||||
|
[x] progressively update comment
|
||||||
|
[] don't allow rejecting prs
|
||||||
|
[] fix pnpm caching
|
||||||
|
[] try to avoid claude narrating the initial comment
|
||||||
|
[] fix prompt to avoid narration like "I just read all tools from MCP server"
|
||||||
|
[] investigate including terminal output from bash commands as collapsed groups
|
||||||
|
[] avoid exposing env
|
||||||
+83
-5
@@ -5,6 +5,29 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
|
const isDebugEnabled = process.env.LOG_LEVEL === "debug";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a collapsed group (GitHub Actions) or regular group (local)
|
||||||
|
*/
|
||||||
|
function startGroup(name: string): void {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.startGroup(name);
|
||||||
|
} else {
|
||||||
|
console.group(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a collapsed group
|
||||||
|
*/
|
||||||
|
function endGroup(): void {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.endGroup();
|
||||||
|
} else {
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a formatted box with text (for console output)
|
* Print a formatted box with text (for console output)
|
||||||
@@ -87,7 +110,12 @@ function box(
|
|||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
core.info(boxString(text, options));
|
const boxContent = boxString(text, options);
|
||||||
|
core.info(boxContent);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
// Add as markdown code block for summary (no headers)
|
||||||
|
core.summary.addRaw(`\`\`\`\n${text}\n\`\`\`\n`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,10 +143,10 @@ async function summaryTable(
|
|||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
const summary = core.summary;
|
const summary = core.summary;
|
||||||
if (title) {
|
if (title) {
|
||||||
summary.addHeading(title);
|
summary.addRaw(`**${title}**\n\n`);
|
||||||
}
|
}
|
||||||
summary.addTable(formattedRows);
|
summary.addTable(formattedRows);
|
||||||
await summary.write();
|
// Note: Don't write immediately, let it accumulate with other summary content
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also log to console for visibility
|
// Also log to console for visibility
|
||||||
@@ -133,7 +161,11 @@ async function summaryTable(
|
|||||||
* Print a separator line
|
* Print a separator line
|
||||||
*/
|
*/
|
||||||
function separator(length: number = 50): void {
|
function separator(length: number = 50): void {
|
||||||
core.info("─".repeat(length));
|
const separatorText = "─".repeat(length);
|
||||||
|
core.info(separatorText);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.summary.addRaw(`---\n`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,6 +177,9 @@ export const log = {
|
|||||||
*/
|
*/
|
||||||
info: (message: string): void => {
|
info: (message: string): void => {
|
||||||
core.info(message);
|
core.info(message);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.summary.addRaw(`${message}\n`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,6 +187,9 @@ export const log = {
|
|||||||
*/
|
*/
|
||||||
warning: (message: string): void => {
|
warning: (message: string): void => {
|
||||||
core.warning(message);
|
core.warning(message);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.summary.addRaw(`⚠️ ${message}\n`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,13 +197,33 @@ export const log = {
|
|||||||
*/
|
*/
|
||||||
error: (message: string): void => {
|
error: (message: string): void => {
|
||||||
core.error(message);
|
core.error(message);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.summary.addRaw(`❌ ${message}\n`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print success message
|
* Print success message
|
||||||
*/
|
*/
|
||||||
success: (message: string): void => {
|
success: (message: string): void => {
|
||||||
core.info(`✅ ${message}`);
|
const successMessage = `✅ ${message}`;
|
||||||
|
core.info(successMessage);
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.summary.addRaw(`${successMessage}\n`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print debug message (only if LOG_LEVEL=debug)
|
||||||
|
*/
|
||||||
|
debug: (message: string): void => {
|
||||||
|
if (isDebugEnabled) {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.debug(message);
|
||||||
|
} else {
|
||||||
|
core.info(`[DEBUG] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,4 +241,24 @@ export const log = {
|
|||||||
* Print a separator line
|
* Print a separator line
|
||||||
*/
|
*/
|
||||||
separator,
|
separator,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write all accumulated summary content to the job summary
|
||||||
|
* Call this at the end of execution to finalize the summary
|
||||||
|
*/
|
||||||
|
writeSummary: async (): Promise<void> => {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
await core.summary.write();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a collapsed group (GitHub Actions) or regular group (local)
|
||||||
|
*/
|
||||||
|
startGroup,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a collapsed group
|
||||||
|
*/
|
||||||
|
endGroup,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
+6
-5
@@ -1,5 +1,6 @@
|
|||||||
import { createSign } from "node:crypto";
|
import { createSign } from "node:crypto";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
import { log } from "./cli.ts";
|
||||||
|
|
||||||
export interface InstallationToken {
|
export interface InstallationToken {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -53,14 +54,14 @@ function isGitHubActionsEnvironment(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function acquireTokenViaOIDC(): Promise<string> {
|
async function acquireTokenViaOIDC(): Promise<string> {
|
||||||
core.info("Generating OIDC token...");
|
log.info("Generating OIDC token...");
|
||||||
|
|
||||||
const oidcToken = await core.getIDToken("pullfrog-api");
|
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||||
core.info("OIDC token generated successfully");
|
log.info("OIDC token generated successfully");
|
||||||
|
|
||||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||||
|
|
||||||
core.info("Exchanging OIDC token for installation token...");
|
log.info("Exchanging OIDC token for installation token...");
|
||||||
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -77,7 +78,7 @@ async function acquireTokenViaOIDC(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tokenData = (await tokenResponse.json()) as InstallationToken;
|
const tokenData = (await tokenResponse.json()) as InstallationToken;
|
||||||
core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||||
|
|
||||||
return tokenData.token;
|
return tokenData.token;
|
||||||
}
|
}
|
||||||
@@ -238,7 +239,7 @@ export async function setupGitHubInstallationToken(): Promise<string> {
|
|||||||
const existingToken = checkExistingToken();
|
const existingToken = checkExistingToken();
|
||||||
if (existingToken) {
|
if (existingToken) {
|
||||||
core.setSecret(existingToken);
|
core.setSecret(existingToken);
|
||||||
core.info("Using provided GitHub installation token");
|
log.info("Using provided GitHub installation token");
|
||||||
return existingToken;
|
return existingToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
/**
|
|
||||||
* Centralized debug logging utility
|
|
||||||
* Controls debug behavior based on LOG_LEVEL environment variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
const isDebugEnabled = process.env.LOG_LEVEL === "debug";
|
|
||||||
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if debug logging is enabled
|
|
||||||
*/
|
|
||||||
export function isDebug(): boolean {
|
|
||||||
return isDebugEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log debug message if debug is enabled
|
|
||||||
* Uses core.debug() in GitHub Actions, console.log() locally
|
|
||||||
*/
|
|
||||||
export function debugLog(message: string): void {
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
if (isGitHubActions) {
|
|
||||||
core.debug(message);
|
|
||||||
} else {
|
|
||||||
console.log(`[DEBUG] ${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
-11
@@ -1,5 +1,6 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { existsSync, rmSync } from "node:fs";
|
import { existsSync, rmSync } from "node:fs";
|
||||||
|
import { log } from "./cli.ts";
|
||||||
import type { RepoContext } from "./github.ts";
|
import type { RepoContext } from "./github.ts";
|
||||||
|
|
||||||
export interface SetupOptions {
|
export interface SetupOptions {
|
||||||
@@ -20,49 +21,72 @@ export function setupTestRepo(options: SetupOptions): void {
|
|||||||
|
|
||||||
if (existsSync(tempDir)) {
|
if (existsSync(tempDir)) {
|
||||||
if (forceClean) {
|
if (forceClean) {
|
||||||
console.log("🗑️ Removing existing .temp directory...");
|
log.info("🗑️ Removing existing .temp directory...");
|
||||||
rmSync(tempDir, { recursive: true, force: true });
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
|
||||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||||
} else {
|
} else {
|
||||||
console.log("📦 Resetting existing .temp repository...");
|
log.info("📦 Resetting existing .temp repository...");
|
||||||
execSync("git reset --hard HEAD && git clean -fd", {
|
execSync("git reset --hard HEAD && git clean -fd", {
|
||||||
cwd: tempDir,
|
cwd: tempDir,
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup git configuration to avoid identity errors
|
* Setup git configuration to avoid identity errors
|
||||||
|
* Only runs in GitHub Actions environment to avoid overwriting local git config
|
||||||
*/
|
*/
|
||||||
export function setupGitConfig(): void {
|
export function setupGitConfig(): void {
|
||||||
console.log("🔧 Setting up git configuration...");
|
// Only set up git config in GitHub Actions environment
|
||||||
execSync('git config user.email "action@pullfrog.ai"', { stdio: "inherit" });
|
// In local development, use the user's existing git config
|
||||||
execSync('git config user.name "Pullfrog Action"', { stdio: "inherit" });
|
if (!process.env.GITHUB_ACTIONS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("🔧 Setting up git configuration...");
|
||||||
|
try {
|
||||||
|
execSync('git config user.email "action@pullfrog.ai"', { stdio: "pipe" });
|
||||||
|
execSync('git config user.name "Pullfrog Action"', { stdio: "pipe" });
|
||||||
|
log.debug("setupGitConfig: ✓ Git configuration set successfully");
|
||||||
|
} catch (error) {
|
||||||
|
// If git config fails, log warning but don't fail the action
|
||||||
|
// This can happen if we're not in a git repo or git isn't available
|
||||||
|
log.warning(
|
||||||
|
`Failed to set git config: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup git authentication using GitHub token
|
* Setup git authentication using GitHub token
|
||||||
|
* Only runs in GitHub Actions environment to avoid breaking local git remotes
|
||||||
*/
|
*/
|
||||||
export function setupGitAuth(githubToken: string, repoContext: RepoContext): void {
|
export function setupGitAuth(githubToken: string, repoContext: RepoContext): void {
|
||||||
console.log("🔐 Setting up git authentication...");
|
// Only set up git auth in GitHub Actions environment
|
||||||
|
// In local testing, this would overwrite the real git remote with fake credentials
|
||||||
|
if (!process.env.GITHUB_ACTIONS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("🔐 Setting up git authentication...");
|
||||||
|
|
||||||
// Remove existing git auth headers that actions/checkout might have set
|
// Remove existing git auth headers that actions/checkout might have set
|
||||||
try {
|
try {
|
||||||
execSync("git config --unset-all http.https://github.com/.extraheader", { stdio: "inherit" });
|
execSync("git config --unset-all http.https://github.com/.extraheader", { stdio: "inherit" });
|
||||||
console.log("✓ Removed existing authentication headers");
|
log.info("✓ Removed existing authentication headers");
|
||||||
} catch {
|
} catch {
|
||||||
console.log("No existing authentication headers to remove");
|
log.info("No existing authentication headers to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update remote URL to embed the token
|
// Update remote URL to embed the token
|
||||||
const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoContext.owner}/${repoContext.name}.git`;
|
const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoContext.owner}/${repoContext.name}.git`;
|
||||||
execSync(`git remote set-url origin "${remoteUrl}"`, { stdio: "inherit" });
|
execSync(`git remote set-url origin "${remoteUrl}"`, { stdio: "inherit" });
|
||||||
console.log("✓ Updated remote URL with authentication token");
|
log.info("✓ Updated remote URL with authentication token");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user