diff --git a/entry.js b/entry.js
index 41cb801..9b1f6e2 100755
--- a/entry.js
+++ b/entry.js
@@ -18,8 +18,8 @@ ${s}
`)},error:e=>{a.error(e),l&&a.summary.addRaw(`\u274C ${e}
`)},success:e=>{let t=`\u2705 ${e}`;a.info(t),l&&a.summary.addRaw(`${t}
`)},debug:e=>{X&&(l?a.debug(e):a.info(`[DEBUG] ${e}`))},box:oe,summaryTable:ne,separator:re,writeSummary:async()=>{l&&await a.summary.write()},startGroup:Z,endGroup:ee};import{createSign as ie}from"node:crypto";import*as w from"@actions/core";function se(){let e=w.getInput("github_installation_token"),t=process.env.GITHUB_INSTALLATION_TOKEN;return e||t||null}function ae(){return!!process.env.GITHUB_ACTIONS}async function ce(){i.info("Generating OIDC token...");let e=await w.getIDToken("pullfrog-api");i.info("OIDC token generated successfully");let t=process.env.API_URL||"https://pullfrog.ai";i.info("Exchanging OIDC token for installation token...");let o=await fetch(`${t}/api/github/installation-token`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"}});if(!o.ok){let s=await o.text();throw new Error(`Token exchange failed: ${o.status} ${o.statusText} - ${s}`)}let n=await o.json();return i.info(`Installation token obtained for ${n.repository||"all repositories"}`),n.token}var E=e=>Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),pe=(e,t)=>{let o=Math.floor(Date.now()/1e3),n={iat:o-60,exp:o+300,iss:e},r=E(JSON.stringify({alg:"RS256",typ:"JWT"})),p=E(JSON.stringify(n)),c=`${r}.${p}`,u=ie("RSA-SHA256").update(c).sign(t,"base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");return`${c}.${u}`},S=async(e,t={})=>{let{method:o="GET",headers:n={},body:s}=t,r=`https://api.github.com${e}`,p={Accept:"application/vnd.github.v3+json","User-Agent":"Pullfrog-Installation-Token-Generator/1.0",...n},c=await fetch(r,{method:o,headers:p,...s&&{body:s}});if(!c.ok){let u=await c.text();throw new Error(`GitHub API request failed: ${c.status} ${c.statusText}
-${u}`)}return c.json()},ue=async(e,t,o)=>{try{return(await S("/installation/repositories",{headers:{Authorization:`token ${e}`}})).repositories.some(s=>s.owner.login===t&&s.name===o)}catch{return!1}},M=async(e,t)=>(await S(`/app/installations/${t}/access_tokens`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})).token,le=async(e,t,o)=>{let n=await S("/app/installations",{headers:{Authorization:`Bearer ${e}`}});for(let s of n)try{let r=await M(e,s.id);if(await ue(r,t,o))return s.id}catch{}throw new Error(`No installation found with access to ${t}/${o}. Ensure the GitHub App is installed on the target repository.`)};async function me(){let e=T(),t={appId:process.env.GITHUB_APP_ID,privateKey:process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g,`
-`),repoOwner:e.owner,repoName:e.name},o=pe(t.appId,t.privateKey),n=await le(o,t.repoOwner,t.repoName);return await M(o,n)}async function de(){return ae()?await ce():await me()}async function O(){let e=se();if(e)return w.setSecret(e),i.info("Using provided GitHub installation token"),e;let t=await de();return w.setSecret(t),process.env.GITHUB_INSTALLATION_TOKEN=t,t}function T(){let e=process.env.GITHUB_REPOSITORY;if(!e)throw new Error("GITHUB_REPOSITORY environment variable is required");let[t,o]=e.split("/");if(!t||!o)throw new Error(`Invalid GITHUB_REPOSITORY format: ${e}. Expected 'owner/repo'`);return{owner:t,name:o}}var x="gh-pullfrog";function G(e){let t=T(),o=`${t.owner}/${t.name}`,n=process.env.GITHUB_ACTION_PATH?`${process.env.GITHUB_ACTION_PATH}/entry.js`:`${process.cwd()}/entry.js`;return{[x]:{command:"node",args:["--input-type=module","-e",`import('${n.replace(/'/g,"\\'")}').then(m => m.createMcpServer())`],env:{GITHUB_INSTALLATION_TOKEN:e,GITHUB_REPOSITORY:o}}}}var H=`
+${u}`)}return c.json()},ue=async(e,t,o)=>{try{return(await S("/installation/repositories",{headers:{Authorization:`token ${e}`}})).repositories.some(s=>s.owner.login===t&&s.name===o)}catch{return!1}},O=async(e,t)=>(await S(`/app/installations/${t}/access_tokens`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})).token,le=async(e,t,o)=>{let n=await S("/app/installations",{headers:{Authorization:`Bearer ${e}`}});for(let s of n)try{let r=await O(e,s.id);if(await ue(r,t,o))return s.id}catch{}throw new Error(`No installation found with access to ${t}/${o}. Ensure the GitHub App is installed on the target repository.`)};async function me(){let e=T(),t={appId:process.env.GITHUB_APP_ID,privateKey:process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g,`
+`),repoOwner:e.owner,repoName:e.name},o=pe(t.appId,t.privateKey),n=await le(o,t.repoOwner,t.repoName);return await O(o,n)}async function de(){return ae()?await ce():await me()}async function G(){let e=se();if(e)return w.setSecret(e),i.info("Using provided GitHub installation token"),e;let t=await de();return w.setSecret(t),process.env.GITHUB_INSTALLATION_TOKEN=t,t}function T(){let e=process.env.GITHUB_REPOSITORY;if(!e)throw new Error("GITHUB_REPOSITORY environment variable is required");let[t,o]=e.split("/");if(!t||!o)throw new Error(`Invalid GITHUB_REPOSITORY format: ${e}. Expected 'owner/repo'`);return{owner:t,name:o}}var x="gh-pullfrog";function M(e){let t=T(),o=`${t.owner}/${t.name}`,n=process.env.GITHUB_ACTION_PATH?`${process.env.GITHUB_ACTION_PATH}/entry.js`:`${process.cwd()}/entry.js`;return{[x]:{command:"node",args:["--input-type=module","-e",`import('${n.replace(/'/g,"\\'")}').then(m => m.createMcpServer())`],env:{GITHUB_INSTALLATION_TOKEN:e,GITHUB_REPOSITORY:o}}}}var H=`
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 ${x}
@@ -57,4 +57,4 @@ You are a highly intelligent, no-nonsense senior-level software engineering agen
`;var N={run:async({prompt:e,mcpServers:t,apiKey:o})=>{process.env.ANTHROPIC_API_KEY=o;let n=fe({prompt:`${H}
${e}`,options:{permissionMode:"bypassPermissions",mcpServers:t}});for await(let s of n){let r=ge[s.type];await r(s)}return{success:!0,output:""}}},C=new Set,ge={assistant:e=>{if(e.message?.content){for(let t of e.message.content)if(t.type==="text"&&t.text?.trim())i.box(t.text.trim(),{title:"Claude"});else if(t.type==="tool_use"&&(i.info(`\u2192 ${t.name}`),t.name==="bash"&&t.id&&C.add(t.id),t.input)){let o=t.input;if(o.description&&i.info(` \u2514\u2500 ${o.description}`),o.command&&i.info(` \u2514\u2500 command: ${o.command}`),o.file_path&&i.info(` \u2514\u2500 file: ${o.file_path}`),o.content){let n=o.content.length>100?`${o.content.substring(0,100)}...`:o.content;i.info(` \u2514\u2500 content: ${n}`)}o.query&&i.info(` \u2514\u2500 query: ${o.query}`),o.pattern&&i.info(` \u2514\u2500 pattern: ${o.pattern}`),o.url&&i.info(` \u2514\u2500 url: ${o.url}`),o.edits&&Array.isArray(o.edits)&&(i.info(` \u2514\u2500 edits: ${o.edits.length} changes`),o.edits.forEach((n,s)=>{n.file_path&&i.info(` ${s+1}. ${n.file_path}`)})),o.task&&i.info(` \u2514\u2500 task: ${o.task}`),o.bash_command&&i.info(` \u2514\u2500 bash_command: ${o.bash_command}`)}}},user:e=>{if(e.message?.content){for(let t of e.message.content)if(t.type==="tool_result"){let o=t.tool_use_id;if(o&&C.has(o)){let s=typeof t.content=="string"?t.content:Array.isArray(t.content)?t.content.map(r=>typeof r=="string"?r:r.text||JSON.stringify(r)).join(`
-`):String(t.content);i.startGroup("bash output"),t.is_error?i.warning(s):i.info(s),i.endGroup(),C.delete(o)}else if(t.is_error){let s=typeof t.content=="string"?t.content:String(t.content);i.warning(`Tool error: ${s}`)}}}},result:async e=>{e.subtype==="success"?await i.summaryTable([[{data:"Cost",header:!0},{data:"Input Tokens",header:!0},{data:"Output Tokens",header:!0}],[`$${e.total_cost_usd?.toFixed(4)||"0.0000"}`,String(e.usage?.input_tokens||0),String(e.usage?.output_tokens||0)]]):e.subtype==="error_max_turns"?i.error(`Max turns reached: ${JSON.stringify(e)}`):e.subtype==="error_during_execution"?i.error(`Execution error: ${JSON.stringify(e)}`):i.error(`Failed: ${JSON.stringify(e)}`)},system:()=>{},stream_event:()=>{}};import{execSync as I}from"node:child_process";function q(){i.info("\u{1F527} Setting up git configuration..."),I('git config user.email "action@pullfrog.ai"',{stdio:"inherit"}),I('git config user.name "Pullfrog Action"',{stdio:"inherit"})}function U(e,t){if(!process.env.GITHUB_ACTIONS){i.info("\u26A0\uFE0F Skipping git authentication setup (not in GitHub Actions)");return}i.info("\u{1F510} Setting up git authentication...");try{I("git config --unset-all http.https://github.com/.extraheader",{stdio:"inherit"}),i.info("\u2713 Removed existing authentication headers")}catch{i.info("No existing authentication headers to remove")}let o=`https://x-access-token:${e}@github.com/${t.owner}/${t.name}.git`;I(`git remote set-url origin "${o}"`,{stdio:"inherit"}),i.info("\u2713 Updated remote URL with authentication token")}var D=he({prompt:"string","anthropic_api_key?":"string | undefined"});async function B(e){try{i.info("Starting agent run..."),q();let t=await O(),o=T();U(t,o);let n=G(t);i.debug(`\u{1F4CB} MCP Config: ${JSON.stringify(n,null,2)}`),i.info("Running Claude Agent SDK..."),i.box(e.prompt,{title:"Prompt"});let s=await N.run({prompt:e.prompt,mcpServers:n,githubInstallationToken:t,apiKey:e.anthropic_api_key});return s.success?(i.success("Task complete."),await i.writeSummary(),{success:!0,output:s.output||""}):{success:!1,error:s.error||"Agent execution failed",output:s.output}}catch(t){let o=t instanceof Error?t.message:"Unknown error occurred";return i.error(o),await i.writeSummary(),{success:!1,error:o}}}import{FastMCP as Ce}from"fastmcp";import{type as _}from"arktype";import{cached as ye}from"@ark/util";import{Octokit as be}from"@octokit/rest";var we=ye(()=>{let e=process.env.GITHUB_INSTALLATION_TOKEN;if(!e)throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");return{...T(),octokit:new be({auth:e})}}),m=e=>e,L=(e,t)=>{for(let o of t)e.addTool(o);return e},d=e=>async t=>{try{let o=we(),n=await e(t,o);return Te(n)}catch(o){return xe(o)}},Te=e=>({content:[{type:"text",text:JSON.stringify(e,null,2)}]}),xe=e=>({content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0});var _e=_({issueNumber:_.number.describe("the issue number to comment on"),body:_.string.describe("the comment body content")}),K=m({name:"create_issue_comment",description:"Create a comment on a GitHub issue",parameters:_e,execute:d(async({issueNumber:e,body:t},o)=>{let n=await o.octokit.rest.issues.createComment({owner:o.owner,repo:o.name,issue_number:e,body:t});return{success:!0,commentId:n.data.id,url:n.data.html_url,body:n.data.body}})}),ke=_({commentId:_.number.describe("the ID of the comment to edit"),body:_.string.describe("the new comment body content")}),j=m({name:"edit_issue_comment",description:"Edit a GitHub issue comment by its ID",parameters:ke,execute:d(async({commentId:e,body:t},o)=>{let n=await o.octokit.rest.issues.updateComment({owner:o.owner,repo:o.name,comment_id:e,body:t});return{success:!0,commentId:n.data.id,url:n.data.html_url,body:n.data.body,updatedAt:n.data.updated_at}})});import{type as R}from"arktype";var ve=R({title:R.string.describe("the title of the issue"),body:R.string.describe("the body content of the issue"),labels:R.string.array().describe("optional array of label names to apply to the issue").optional(),assignees:R.string.array().describe("optional array of usernames to assign to the issue").optional()}),J=m({name:"create_issue",description:"Create a new GitHub issue",parameters:ve,execute:d(async({title:e,body:t,labels:o,assignees:n},s)=>{let r=await s.octokit.rest.issues.create({owner:s.owner,repo:s.name,title:e,body:t,labels:o??[],assignees:n??[]});return{success:!0,issueId:r.data.id,number:r.data.number,url:r.data.html_url,title:r.data.title,state:r.data.state,labels:r.data.labels?.map(p=>typeof p=="string"?p:p.name),assignees:r.data.assignees?.map(p=>p.login)}})});import{execSync as Re}from"node:child_process";import{type as $}from"arktype";var Ie=$({title:$.string.describe("the title of the pull request"),body:$.string.describe("the body content of the pull request"),base:$.string.describe("the base branch to merge into (e.g., 'main')")}),z=m({name:"create_pull_request",description:"Create a pull request from the current branch",parameters:Ie,execute:d(async({title:e,body:t,base:o},n)=>{let s=Re("git rev-parse --abbrev-ref HEAD",{encoding:"utf8"}).trim();i.info(`Current branch: ${s}`);let r=await n.octokit.rest.pulls.create({owner:n.owner,repo:n.name,title:e,body:t,head:s,base:o});return{success:!0,pullRequestId:r.data.id,number:r.data.number,url:r.data.html_url,title:r.data.title,head:r.data.head.ref,base:r.data.base.ref}})});import{execSync as A}from"node:child_process";import{type as Y}from"arktype";var $e=Y({pull_number:Y.number.describe("The pull request number to fetch")}),F=m({name:"get_pull_request",description:"Retrieve PR information and automatically prepare the repository for review by fetching and checking out the PR branch.",parameters:$e,execute:d(async({pull_number:e},t)=>{let n=(await t.octokit.rest.pulls.get({owner:t.owner,repo:t.name,pull_number:e})).data,s=n.base.ref,r=n.head.ref;if(!s)throw new Error(`Base branch not found for PR #${e}`);return i.info(`Fetching base branch: origin/${s}`),A(`git fetch origin ${s} --depth=20`,{stdio:"inherit"}),i.info(`Fetching PR branch: origin/${r}`),A(`git fetch origin ${r}`,{stdio:"inherit"}),i.info(`Checking out PR branch: origin/${r}`),A(`git checkout origin/${r}`,{stdio:"inherit"}),{number:n.number,url:n.html_url,title:n.title,state:n.state,draft:n.draft,merged:n.merged,base:s,head:r}})});import{type as f}from"arktype";var Se=f({pull_number:f.number.describe("The pull request number to review"),event:f.enumerated("APPROVE","REQUEST_CHANGES","COMMENT").describe("'APPROVE', 'REQUEST_CHANGES', or 'COMMENT' (the review action)"),body:f.string.describe("Brief summary or general feedback that doesn't apply to specific code locations. Keep it concise - most feedback should be in the 'comments' array.").optional(),commit_id:f.string.describe("Optional SHA of the commit being reviewed. Defaults to latest.").optional(),comments:f({path:f.string.describe("The file path to comment on (relative to repo root)"),line:f.number.describe("The line number in the file (use line numbers from the diff - usually the RIGHT side/new code)"),side:f.enumerated("LEFT","RIGHT").describe("Side of the diff: LEFT (old code) or RIGHT (new code). Defaults to RIGHT if not provided.").optional(),body:f.string.describe("The comment text for this specific line"),start_line:f.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/