diff --git a/action.yml b/action.yml index f056c44..93fde08 100644 --- a/action.yml +++ b/action.yml @@ -35,6 +35,10 @@ inputs: required: false default: ${{ github.token }} +outputs: + result: + description: "It's set when the prompt explicitly requests it. It can be used to capture an actionable output for the next step in the workflow." + runs: using: "node24" main: "entry" diff --git a/entry b/entry index cacdce9..e137edf 100755 --- a/entry +++ b/entry @@ -19763,7 +19763,7 @@ var require_core = __commonJS({ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); } exports.getBooleanInput = getBooleanInput; - function setOutput(name, value2) { + function setOutput2(name, value2) { const filePath = process.env["GITHUB_OUTPUT"] || ""; if (filePath) { return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value2)); @@ -19771,7 +19771,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); process.stdout.write(os2.EOL); (0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value2)); } - exports.setOutput = setOutput; + exports.setOutput = setOutput2; function setCommandEcho(enabled) { (0, command_1.issue)("echo", enabled ? "on" : "off"); } @@ -142448,6 +142448,22 @@ function AddLabelsTool(ctx) { }); } +// mcp/output.ts +var SetOutputParams = type({ + value: type.string.describe("the output value to expose as a GitHub Action output") +}); +function SetOutputTool(ctx) { + return tool({ + name: "set_output", + description: "Set the action output for consumption by subsequent workflow steps. The value will be available as the 'result' output of the action.", + parameters: SetOutputParams, + execute: execute(async (params) => { + ctx.toolState.output = params.value; + return { success: true }; + }) + }); +} + // mcp/pr.ts var PullRequest = type({ title: type.string.describe("the title of the pull request"), @@ -143218,7 +143234,8 @@ async function startMcpHttpServer(ctx) { CreateBranchTool(ctx), CommitFilesTool(ctx), PushBranchTool(ctx), - UploadFileTool(ctx) + UploadFileTool(ctx), + SetOutputTool(ctx) ]; if (ctx.payload.bash === "restricted") { tools.push(BashTool(ctx)); @@ -162114,6 +162131,12 @@ function getShellInstructions(bash) { } } } +function getStandaloneModeInstructions(trigger) { + if (trigger !== "unknown") { + return ""; + } + return `**Standalone mode**: You are running as a step in a user-defined CI workflow. When you complete your task, call \`${ghPullfrogMcpName}/set_output\` with the main result of your work (generated content, summary of changes, analysis results, etc.). This makes it available as a GitHub Action output named \`result\` for subsequent workflow steps to consume.`; +} function resolveInstructions(ctx) { const eventTitleBody = buildEventTitleBody(ctx.payload.event); const eventMetadata = buildEventMetadata(ctx.payload.event); @@ -162172,6 +162195,8 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g ${getShellInstructions(ctx.payload.bash)} +${getStandaloneModeInstructions(ctx.payload.event.trigger)} + **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Commenting style**: When posting comments via ${ghPullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable\u2014do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question." @@ -162411,7 +162436,7 @@ function resolvePayload(repoSettings) { } // utils/run.ts -async function handleAgentResult(result) { +function handleAgentResult(result) { if (!result.success) { return { success: false, @@ -162762,7 +162787,10 @@ async function main() { if (toolState.lastProgressBody) { await writeSummary(toolState.lastProgressBody); } - return handleAgentResult(result); + return { + ...handleAgentResult(result), + result: toolState.output + }; } catch (_) { var _error = _, _hasError = true; } finally { @@ -162796,6 +162824,9 @@ async function run() { if (!result.success) { throw new Error(result.error || "Agent execution failed"); } + if (result.result) { + core5.setOutput("result", result.result); + } } catch (error50) { const errorMessage = error50 instanceof Error ? error50.message : "Unknown error occurred"; core5.setFailed(`Action failed: ${errorMessage}`); diff --git a/entry.ts b/entry.ts index 61a87fe..32f13c1 100644 --- a/entry.ts +++ b/entry.ts @@ -15,6 +15,10 @@ async function run(): Promise { if (!result.success) { throw new Error(result.error || "Agent execution failed"); } + + if (result.result) { + core.setOutput("result", result.result); + } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; core.setFailed(`Action failed: ${errorMessage}`); diff --git a/main.ts b/main.ts index 8820bf0..7befb41 100644 --- a/main.ts +++ b/main.ts @@ -25,6 +25,7 @@ export interface MainResult { success: boolean; output?: string | undefined; error?: string | undefined; + result?: string | undefined; } export async function main(): Promise { @@ -153,7 +154,10 @@ export async function main(): Promise { await writeSummary(toolState.lastProgressBody); } - return handleAgentResult(result); + return { + ...handleAgentResult(result), + result: toolState.output, + }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; log.error(errorMessage); diff --git a/mcp/README.md b/mcp/README.md index 3e959ed..24b37f0 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -154,11 +154,45 @@ await mcp.call("gh_pullfrog/reply_to_review_comment", { }); ``` +### output tools + +#### `set_output` +set the action output for consumption by subsequent workflow steps. useful when pullfrog is used as a step in a user-defined CI workflow (e.g., generating release notes). + +**parameters:** +- `value` (string): the output value to expose + +**returns:** +- `success`: true on success + +the value will be available as the `result` output of the action, accessible via `${{ steps..outputs.result }}`. + +**example:** +```typescript +// when generating content for downstream consumption +await mcp.call("gh_pullfrog/set_output", { + value: "## Release Notes\n\n- Added new feature X\n- Fixed bug Y" +}); +``` + +**usage in workflow:** +```yaml +- uses: pullfrog/pullfrog@v1 + id: notes + with: + prompt: "Generate release notes for v2.0.0" + +- uses: softprops/action-gh-release@v1 + with: + body: ${{ steps.notes.outputs.result }} +``` + ### other tools see individual files for documentation on other tools: - `comment.ts` - create, edit, and update comments - `issue.ts` - create issues +- `output.ts` - set action output for workflow consumption - `pr.ts` - create pull requests - `prInfo.ts` - get pull request information - `review.ts` - create pull request reviews diff --git a/mcp/output.ts b/mcp/output.ts new file mode 100644 index 0000000..f626297 --- /dev/null +++ b/mcp/output.ts @@ -0,0 +1,20 @@ +import { type } from "arktype"; +import type { ToolContext } from "./server.ts"; +import { execute, tool } from "./shared.ts"; + +export const SetOutputParams = type({ + value: type.string.describe("the output value to expose as a GitHub Action output"), +}); + +export function SetOutputTool(ctx: ToolContext) { + return tool({ + name: "set_output", + description: + "Set the action output for consumption by subsequent workflow steps. The value will be available as the 'result' output of the action.", + parameters: SetOutputParams, + execute: execute(async (params) => { + ctx.toolState.output = params.value; + return { success: true }; + }), + }); +} diff --git a/mcp/server.ts b/mcp/server.ts index be157e7..fd9740f 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -32,6 +32,7 @@ export interface ToolState { progressCommentId: number | null; lastProgressBody?: string; wasUpdated?: boolean; + output?: string; } import type { ResolveRunResult } from "../utils/workflow.ts"; @@ -85,6 +86,7 @@ import { GetIssueCommentsTool } from "./issueComments.ts"; import { GetIssueEventsTool } from "./issueEvents.ts"; import { IssueInfoTool } from "./issueInfo.ts"; import { AddLabelsTool } from "./labels.ts"; +import { SetOutputTool } from "./output.ts"; import { CreatePullRequestTool } from "./pr.ts"; import { PullRequestInfoTool } from "./prInfo.ts"; import { CreatePullRequestReviewTool } from "./review.ts"; @@ -179,6 +181,7 @@ export async function startMcpHttpServer( CommitFilesTool(ctx), PushBranchTool(ctx), UploadFileTool(ctx), + SetOutputTool(ctx), ]; // only add BashTool when bash is "restricted" diff --git a/utils/instructions.ts b/utils/instructions.ts index 5f6458b..88d15dd 100644 --- a/utils/instructions.ts +++ b/utils/instructions.ts @@ -100,6 +100,13 @@ function getShellInstructions(bash: ResolvedPayload["bash"]): string { } } +function getStandaloneModeInstructions(trigger: string): string { + if (trigger !== "unknown") { + return ""; + } + return `**Standalone mode**: You are running as a step in a user-defined CI workflow. When you complete your task, call \`${ghPullfrogMcpName}/set_output\` with the main result of your work (generated content, summary of changes, analysis results, etc.). This makes it available as a GitHub Action output named \`result\` for subsequent workflow steps to consume.`; +} + export interface ResolvedInstructions { full: string; system: string; @@ -187,6 +194,8 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g ${getShellInstructions(ctx.payload.bash)} +${getStandaloneModeInstructions(ctx.payload.event.trigger)} + **Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished. **Commenting style**: When posting comments via ${ghPullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable—do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question." diff --git a/utils/run.ts b/utils/run.ts index 04414a5..3a46104 100644 --- a/utils/run.ts +++ b/utils/run.ts @@ -2,7 +2,7 @@ import type { AgentResult } from "../agents/shared.ts"; import type { MainResult } from "../main.ts"; import { log } from "./cli.ts"; -export async function handleAgentResult(result: AgentResult): Promise { +export function handleAgentResult(result: AgentResult): MainResult { if (!result.success) { return { success: false,