Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7d2035315 | |||
| c703ecc4f4 | |||
| b05d1bfc53 | |||
| f765a0878d | |||
| e3a7b09df4 |
+3
-1
@@ -1,13 +1,15 @@
|
||||
import type { AgentName } from "../external.ts";
|
||||
import { claude } from "./claude.ts";
|
||||
import { codex } from "./codex.ts";
|
||||
import { cursor } from "./cursor.ts";
|
||||
import { gemini } from "./gemini.ts";
|
||||
import type { Agent } from "./shared.ts";
|
||||
|
||||
export const agents = {
|
||||
claude,
|
||||
codex,
|
||||
cursor,
|
||||
gemini,
|
||||
} as const;
|
||||
} satisfies Record<AgentName, Agent>;
|
||||
|
||||
export type AgentInputKey = (typeof agents)[keyof typeof agents]["inputKeys"][number];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Payload } from "../external.ts";
|
||||
import { ghPullfrogMcpName } from "../mcp/index.ts";
|
||||
import { ghPullfrogMcpName } from "../external.ts";
|
||||
import { modes } from "../modes.ts";
|
||||
|
||||
export const addInstructions = (payload: Payload) =>
|
||||
|
||||
+33
-1
@@ -1,6 +1,38 @@
|
||||
import type { AgentName } from "./main.ts";
|
||||
/**
|
||||
* ⚠️ NO IMPORTS except modes.ts - this file is imported by Next.js and must avoid pulling in backend code.
|
||||
* All shared constants, types, and data used by both the Next.js app and the action runtime live here.
|
||||
* Other files in action/ re-export from this file for backward compatibility.
|
||||
*/
|
||||
|
||||
import type { Mode } from "./modes.ts";
|
||||
|
||||
// mcp name constant
|
||||
export const ghPullfrogMcpName = "gh-pullfrog";
|
||||
|
||||
// agent manifest - static metadata about available agents
|
||||
export const agentsManifest = {
|
||||
claude: {
|
||||
name: "claude",
|
||||
apiKeys: ["anthropic_api_key"],
|
||||
},
|
||||
codex: {
|
||||
name: "codex",
|
||||
apiKeys: ["openai_api_key"],
|
||||
},
|
||||
cursor: {
|
||||
name: "cursor",
|
||||
apiKeys: ["cursor_api_key"],
|
||||
},
|
||||
gemini: {
|
||||
name: "gemini",
|
||||
apiKeys: ["google_api_key", "gemini_api_key"],
|
||||
},
|
||||
} as const;
|
||||
|
||||
// agent name type - union of agent slugs
|
||||
export type AgentName = keyof typeof agentsManifest;
|
||||
|
||||
// payload type for agent execution
|
||||
export type Payload = {
|
||||
"~pullfrog": true;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
||||
import { flatMorph } from "@ark/util";
|
||||
import { type } from "arktype";
|
||||
import { agents } from "./agents/index.ts";
|
||||
import type { Payload } from "./external.ts";
|
||||
import type { AgentName as AgentNameType, Payload } from "./external.ts";
|
||||
import { createMcpConfigs } from "./mcp/config.ts";
|
||||
import { modes } from "./modes.ts";
|
||||
import packageJson from "./package.json" with { type: "json" };
|
||||
@@ -19,8 +19,9 @@ import {
|
||||
} from "./utils/github.ts";
|
||||
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
|
||||
|
||||
// runtime validation using agents (needed for ArkType)
|
||||
// Note: The AgentName type is defined in external.ts, this is the runtime validator
|
||||
export const AgentName = type.enumerated(...Object.values(agents).map((agent) => agent.name));
|
||||
export type AgentName = typeof AgentName.infer;
|
||||
|
||||
export const AgentInputKey = type.enumerated(
|
||||
...Object.values(agents).flatMap((agent) => agent.inputKeys)
|
||||
@@ -34,7 +35,7 @@ const keyInputDefs = flatMorph(agents, (_, agent) =>
|
||||
export const Inputs = type({
|
||||
prompt: "string",
|
||||
...keyInputDefs,
|
||||
"agent?": AgentName.or("undefined"),
|
||||
"agent?": type.enumerated(...Object.values(agents).map((agent) => agent.name)).or("undefined"),
|
||||
});
|
||||
|
||||
export type Inputs = typeof Inputs.infer;
|
||||
@@ -129,8 +130,8 @@ interface MainContext {
|
||||
githubInstallationToken: string;
|
||||
tokenToRevoke: string | null;
|
||||
repoContext: RepoContext;
|
||||
agentName: AgentName;
|
||||
agent: (typeof agents)[AgentName];
|
||||
agentName: AgentNameType;
|
||||
agent: (typeof agents)[AgentNameType];
|
||||
sharedTempDir: string;
|
||||
mcpLogPath: string;
|
||||
pollInterval: NodeJS.Timeout | null;
|
||||
@@ -156,7 +157,7 @@ async function initializeContext(
|
||||
githubInstallationToken,
|
||||
tokenToRevoke,
|
||||
repoContext,
|
||||
agentName: "claude" as AgentName,
|
||||
agentName: "claude" as AgentNameType,
|
||||
agent: agents.claude,
|
||||
sharedTempDir: "",
|
||||
mcpLogPath: "",
|
||||
@@ -230,7 +231,7 @@ async function installAgentCli(ctx: MainContext): Promise<void> {
|
||||
|
||||
function validateApiKey(ctx: MainContext): void {
|
||||
const matchingInputKey = ctx.agent.inputKeys.find(
|
||||
(inputKey) => ctx.inputs[inputKey as AgentInputKey]
|
||||
(inputKey: string) => ctx.inputs[inputKey as AgentInputKey]
|
||||
);
|
||||
if (!matchingInputKey) {
|
||||
throwMissingApiKeyError({
|
||||
|
||||
+5965
-281
File diff suppressed because one or more lines are too long
+1
-1
@@ -77,7 +77,7 @@ export const CreateWorkingCommentTool = tool({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
issue_number: issueNumber,
|
||||
body: intent,
|
||||
body: `${intent} <img src="https://pullfrog.ai/party-parrot.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />`,
|
||||
});
|
||||
|
||||
workingCommentId = result.data.id;
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@
|
||||
|
||||
import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { fromHere } from "@ark/fs";
|
||||
import { ghPullfrogMcpName } from "../external.ts";
|
||||
import type { Mode } from "../modes.ts";
|
||||
import { parseRepoContext } from "../utils/github.ts";
|
||||
import { ghPullfrogMcpName } from "./index.ts";
|
||||
|
||||
export type McpName = typeof ghPullfrogMcpName;
|
||||
|
||||
@@ -16,9 +16,9 @@ export function createMcpConfigs(githubInstallationToken: string, modes: Mode[])
|
||||
const repoContext = parseRepoContext();
|
||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||
|
||||
// In production (GitHub Actions), mcp-server.js is in same directory as entry.js (where this is bundled)
|
||||
// In production (GitHub Actions), mcp-server is in same directory as entry.js (where this is bundled)
|
||||
// In development, server.ts is in the same directory as this file (config.ts)
|
||||
const serverPath = process.env.GITHUB_ACTIONS ? fromHere("mcp-server.js") : fromHere("server.ts");
|
||||
const serverPath = process.env.GITHUB_ACTIONS ? fromHere("mcp-server") : fromHere("server.ts");
|
||||
|
||||
return {
|
||||
[ghPullfrogMcpName]: {
|
||||
|
||||
+2
-4
@@ -1,4 +1,2 @@
|
||||
// for reasos this can't live alongide config.ts
|
||||
// because its imported into the frontend UI
|
||||
// and we don't want to pull in FastMCP, etc
|
||||
export const ghPullfrogMcpName = "gh-pullfrog";
|
||||
// re-export from external.ts for backward compatibility
|
||||
export { ghPullfrogMcpName } from "../external.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ghPullfrogMcpName } from "./mcp/index.ts";
|
||||
import { ghPullfrogMcpName } from "./external.ts";
|
||||
|
||||
export interface Mode {
|
||||
name: string;
|
||||
@@ -6,13 +6,16 @@ export interface Mode {
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
const initialCommentInstruction = `Use ${ghPullfrogMcpName}/create_working_comment to create an initial Working Comment that contains a SENTENCE FRAGMENT that casually describes the actions you're about to perform. It must be of the form "Starting work on this issue...". You MUST use an -ing verb!`;
|
||||
|
||||
export const modes: Mode[] = [
|
||||
{
|
||||
name: "Plan",
|
||||
description:
|
||||
"Create plans, break down tasks, outline steps, analyze requirements, understand scope of work, or provide task breakdowns",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using ${ghPullfrogMcpName}/create_working_comment saying "I'll {summary of request}"
|
||||
1. ${initialCommentInstruction}
|
||||
|
||||
2. If the request requires understanding the codebase structure, dependencies, or conventions, gather relevant context (read AGENTS.md if it exists, understand how to install dependencies, run tests, run builds, and make changes according to best practices). Skip this step if the prompt is trivial and self-contained.
|
||||
3. Analyze the request and break it down into clear, actionable tasks
|
||||
4. Consider dependencies, potential challenges, and implementation order
|
||||
@@ -25,7 +28,8 @@ export const modes: Mode[] = [
|
||||
description:
|
||||
"Implement, build, create, or develop code changes; make specific changes to files or features; execute a plan; or handle tasks with specific implementation details",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using ${ghPullfrogMcpName}/create_working_comment saying "I'll {summary of request}"
|
||||
1. ${initialCommentInstruction}
|
||||
|
||||
2. If the request requires understanding the codebase structure, dependencies, or conventions, gather relevant context (read AGENTS.md if it exists, understand how to install dependencies, run tests, run builds, and make changes according to best practices). Skip this step if the prompt is trivial and self-contained.
|
||||
3. Understand the requirements and any existing plan
|
||||
4. Make the necessary code changes
|
||||
@@ -38,7 +42,8 @@ export const modes: Mode[] = [
|
||||
description:
|
||||
"Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
||||
prompt: `Follow these steps:
|
||||
1. Create initial response comment using ${ghPullfrogMcpName}/create_working_comment saying "I'll review this"
|
||||
1. ${initialCommentInstruction}
|
||||
|
||||
2. Get PR info with ${ghPullfrogMcpName}/get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
3. View diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments, replace <base> and <head> with 'base' and 'head' from PR info)
|
||||
4. Read files from the checked-out PR branch to understand the implementation
|
||||
@@ -52,7 +57,8 @@ export const modes: Mode[] = [
|
||||
description:
|
||||
"Fallback for tasks that don't fit other workflows, direct prompts via comments, or requests requiring general assistance without a specific workflow pattern",
|
||||
prompt: `Follow these steps:
|
||||
1. Create an initial "Working Comment" using ${ghPullfrogMcpName}/create_working_comment saying "I'll {summary of request}"
|
||||
1. ${initialCommentInstruction}
|
||||
|
||||
2. Perform the requested task. Only take action if you have high confidence that you understand what is being asked. If you are not sure, ask for clarification. Take stock of the tools at your disposal.
|
||||
3. As your work progresses, update your Working Comment to share progress and results using ${ghPullfrogMcpName}/update_working_comment. Do not create additional comments unless you are explicitly asked to do so.
|
||||
4. When you finish the task, update the Working Comment a final time with a summary of the results and links to any created issues, PRs, etc.`,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.108",
|
||||
"version": "0.0.110",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { AgentName } from "../main.ts";
|
||||
import type { AgentName } from "../external.ts";
|
||||
import { log } from "./cli.ts";
|
||||
import type { RepoContext } from "./github.ts";
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { spawnSync } from "node:child_process";
|
||||
import { appendFileSync, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import * as core from "@actions/core";
|
||||
import { table } from "table";
|
||||
|
||||
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||
const isDebugEnabled = process.env.LOG_LEVEL === "debug";
|
||||
@@ -160,6 +161,43 @@ async function summaryTable(
|
||||
core.info(`\n${tableText}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a formatted table using the table package
|
||||
* Also logs to console and GitHub Actions summary
|
||||
*/
|
||||
async function printTable(
|
||||
rows: Array<Array<{ data: string; header?: boolean } | string>>,
|
||||
options?: {
|
||||
title?: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
const { title } = options || {};
|
||||
|
||||
// Convert rows to string arrays for the table package
|
||||
const tableData = rows.map((row) =>
|
||||
row.map((cell) => {
|
||||
if (typeof cell === "string") {
|
||||
return cell;
|
||||
}
|
||||
return cell.data;
|
||||
})
|
||||
);
|
||||
|
||||
const formatted = table(tableData);
|
||||
|
||||
if (title) {
|
||||
core.info(`\n${title}`);
|
||||
}
|
||||
core.info(`\n${formatted}\n`);
|
||||
|
||||
if (isGitHubActions) {
|
||||
if (title) {
|
||||
core.summary.addRaw(`**${title}**\n\n`);
|
||||
}
|
||||
core.summary.addRaw(`\`\`\`\n${formatted}\n\`\`\`\n`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a separator line
|
||||
*/
|
||||
@@ -240,6 +278,11 @@ export const log = {
|
||||
*/
|
||||
summaryTable,
|
||||
|
||||
/**
|
||||
* Print a formatted table using the table package
|
||||
*/
|
||||
table: printTable,
|
||||
|
||||
/**
|
||||
* Print a separator line
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user