Compare commits

...

5 Commits

Author SHA1 Message Date
Colin McDonnell d7d2035315 110 2025-11-19 17:13:28 -08:00
Colin McDonnell c703ecc4f4 Fix MCP discovery 2025-11-19 17:13:14 -08:00
Colin McDonnell b05d1bfc53 Add parrot 2025-11-19 16:52:11 -08:00
Colin McDonnell f765a0878d 0.0.109 2025-11-19 16:02:50 -08:00
Colin McDonnell e3a7b09df4 Move things to external.ts 2025-11-19 16:02:37 -08:00
14 changed files with 11806 additions and 314 deletions
+3 -1
View File
@@ -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 -1
View File
@@ -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) =>
+5732 -8
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env node
/**
* Entry point for GitHub Action
*/
+33 -1
View File
@@ -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;
+8 -7
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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";
+11 -5
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{
"name": "@pullfrog/action",
"version": "0.0.108",
"version": "0.0.110",
"type": "module",
"files": [
"index.js",
+1 -1
View File
@@ -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";
+43
View File
@@ -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
*/