Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 706ce04895 | |||
| 09be8e3068 | |||
| c6c1210fa0 | |||
| 0368512b9e | |||
| 9fb6135fd2 | |||
| bb78e5f94b | |||
| c668578c6f |
+29
-42
@@ -3,6 +3,7 @@ import * as core from "@actions/core";
|
||||
import { createMcpConfig } from "../mcp/config.ts";
|
||||
import { spawn } from "../utils/subprocess.ts";
|
||||
import { boxString, tableString } from "../utils/table.ts";
|
||||
import { instructions } from "./shared.ts";
|
||||
import type { Agent, AgentConfig, AgentResult } from "./types.ts";
|
||||
|
||||
/**
|
||||
@@ -10,7 +11,7 @@ import type { Agent, AgentConfig, AgentResult } from "./types.ts";
|
||||
*/
|
||||
export class ClaudeAgent implements Agent {
|
||||
private apiKey: string;
|
||||
private githubInstallationToken?: string;
|
||||
private githubInstallationToken: string;
|
||||
public runStats = {
|
||||
toolsUsed: 0,
|
||||
turns: 0,
|
||||
@@ -18,9 +19,6 @@ export class ClaudeAgent implements Agent {
|
||||
};
|
||||
|
||||
constructor(config: AgentConfig) {
|
||||
if (!config.apiKey) {
|
||||
throw new Error("Claude agent requires an API key");
|
||||
}
|
||||
this.apiKey = config.apiKey;
|
||||
this.githubInstallationToken = config.githubInstallationToken;
|
||||
}
|
||||
@@ -51,10 +49,7 @@ export class ClaudeAgent implements Agent {
|
||||
try {
|
||||
const result = await spawn({
|
||||
cmd: "bash",
|
||||
args: [
|
||||
"-c",
|
||||
"curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93",
|
||||
],
|
||||
args: ["-c", "curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93"],
|
||||
env: { ANTHROPIC_API_KEY: this.apiKey },
|
||||
timeout: 120000, // 2 minute timeout
|
||||
onStdout: () => {},
|
||||
@@ -62,9 +57,7 @@ export class ClaudeAgent implements Agent {
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(
|
||||
`Installation failed with exit code ${result.exitCode}: ${result.stderr}`
|
||||
);
|
||||
throw new Error(`Installation failed with exit code ${result.exitCode}: ${result.stderr}`);
|
||||
}
|
||||
|
||||
core.info("Claude Code installed successfully");
|
||||
@@ -81,7 +74,17 @@ export class ClaudeAgent implements Agent {
|
||||
|
||||
try {
|
||||
const claudePath = `${process.env.HOME}/.local/bin/claude`;
|
||||
|
||||
const env = {
|
||||
ANTHROPIC_API_KEY: this.apiKey,
|
||||
GITHUB_TOKEN: this.githubInstallationToken,
|
||||
};
|
||||
|
||||
console.log(boxString(prompt, { title: "Prompt" }));
|
||||
|
||||
const mcpConfig = createMcpConfig(this.githubInstallationToken);
|
||||
console.log("📋 MCP Config:", mcpConfig);
|
||||
|
||||
const args = [
|
||||
"--print",
|
||||
"--output-format",
|
||||
@@ -90,22 +93,10 @@ export class ClaudeAgent implements Agent {
|
||||
"--debug",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
"--mcp-config",
|
||||
mcpConfig,
|
||||
];
|
||||
|
||||
if (!this.githubInstallationToken) {
|
||||
throw new Error(
|
||||
"GITHUB_INSTALLATION_TOKEN is required for GitHub integration"
|
||||
);
|
||||
}
|
||||
|
||||
const mcpConfig = createMcpConfig(this.githubInstallationToken);
|
||||
console.log("📋 MCP Config:", mcpConfig);
|
||||
args.push("--mcp-config", mcpConfig);
|
||||
|
||||
const env = {
|
||||
ANTHROPIC_API_KEY: this.apiKey,
|
||||
};
|
||||
|
||||
core.startGroup("🔄 Run details");
|
||||
|
||||
this.runStats = {
|
||||
@@ -121,7 +112,7 @@ export class ClaudeAgent implements Agent {
|
||||
cmd: claudePath,
|
||||
args,
|
||||
env,
|
||||
input: prompt,
|
||||
input: `${instructions} ${prompt}`,
|
||||
timeout: 10 * 60 * 1000, // 10 minutes
|
||||
onStdout: (_chunk) => {
|
||||
processJSONChunk(_chunk, this);
|
||||
@@ -161,8 +152,7 @@ export class ClaudeAgent implements Agent {
|
||||
try {
|
||||
core.endGroup();
|
||||
} catch {}
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to execute Claude Code: ${errorMessage}`,
|
||||
@@ -176,8 +166,15 @@ export class ClaudeAgent implements Agent {
|
||||
*/
|
||||
function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
|
||||
try {
|
||||
// Skip debug lines that start with [DEBUG] or [debug]
|
||||
const trimmedChunk = chunk.trim();
|
||||
if (trimmedChunk.startsWith("[DEBUG]") || trimmedChunk.startsWith("[debug]")) {
|
||||
console.log(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chunk);
|
||||
const parsedChunk = JSON.parse(chunk.trim());
|
||||
const parsedChunk = JSON.parse(trimmedChunk);
|
||||
|
||||
switch (parsedChunk.type) {
|
||||
case "system":
|
||||
@@ -188,12 +185,7 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
|
||||
["model", parsedChunk.model],
|
||||
["cwd", parsedChunk.cwd],
|
||||
["permission_mode", parsedChunk.permissionMode],
|
||||
[
|
||||
"tools",
|
||||
parsedChunk.tools?.length
|
||||
? `${parsedChunk.tools.length} tools`
|
||||
: "none",
|
||||
],
|
||||
["tools", parsedChunk.tools?.length ? `${parsedChunk.tools.length} tools` : "none"],
|
||||
[
|
||||
"mcp_servers",
|
||||
parsedChunk.mcp_servers?.length
|
||||
@@ -220,9 +212,7 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
|
||||
for (const content of parsedChunk.message.content) {
|
||||
if (content.type === "text") {
|
||||
if (content.text.trim()) {
|
||||
core.info(
|
||||
boxString(content.text.trim(), { title: "Claude Code" })
|
||||
);
|
||||
core.info(boxString(content.text.trim(), { title: "Claude Code" }));
|
||||
}
|
||||
} else if (content.type === "tool_use") {
|
||||
if (agent) {
|
||||
@@ -307,10 +297,7 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
|
||||
if (parsedChunk.subtype === "success") {
|
||||
core.info(
|
||||
tableString([
|
||||
[
|
||||
"Cost",
|
||||
`$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`,
|
||||
],
|
||||
["Cost", `$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`],
|
||||
["Input Tokens", parsedChunk.usage?.input_tokens || 0],
|
||||
["Output Tokens", parsedChunk.usage?.output_tokens || 0],
|
||||
["Duration", `${parsedChunk.duration_ms}ms`],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { mcpServerName } 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
|
||||
- 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,
|
||||
also respond to the original comment with a very brief message containing a link to it
|
||||
`;
|
||||
+2
-2
@@ -29,6 +29,6 @@ export interface AgentResult {
|
||||
* Configuration for agent creation
|
||||
*/
|
||||
export interface AgentConfig {
|
||||
apiKey?: string;
|
||||
[key: string]: any;
|
||||
apiKey: string;
|
||||
githubInstallationToken: string;
|
||||
}
|
||||
|
||||
+1
-3
@@ -1,3 +1 @@
|
||||
Use the MCP GitHub comment tool to add a comment containing your best frog joke to GitHub issue https://github.com/pullfrogai/scratch/issues/2.
|
||||
|
||||
Do not use the gh cli. If the mcp tool does not work, bail.
|
||||
create a PR implementing bogosort to https://github.com/pullfrogai/scratch/
|
||||
|
||||
@@ -27,6 +27,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
|
||||
apiKey: inputs.anthropic_api_key!,
|
||||
githubInstallationToken,
|
||||
});
|
||||
|
||||
await agent.install();
|
||||
|
||||
const result = await agent.execute(inputs.prompt);
|
||||
|
||||
+15
-53
@@ -1,7 +1,5 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { type } from "arktype";
|
||||
import { resolveRepoContext } from "../utils/repo-context.ts";
|
||||
import { tool } from "./shared.ts";
|
||||
import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
export const Comment = type({
|
||||
issueNumber: type.number.describe("the issue number to comment on"),
|
||||
@@ -12,55 +10,19 @@ export const CommentTool = tool({
|
||||
name: "create_issue_comment",
|
||||
description: "Create a comment on a GitHub issue",
|
||||
parameters: Comment,
|
||||
execute: async ({ issueNumber, body }: { issueNumber: number; body: string }) => {
|
||||
try {
|
||||
const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
if (!githubInstallationToken) {
|
||||
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
|
||||
}
|
||||
execute: contextualize(async ({ issueNumber, body }, ctx) => {
|
||||
const result = await ctx.octokit.rest.issues.createComment({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
issue_number: issueNumber,
|
||||
body: body,
|
||||
});
|
||||
|
||||
const repoContext = resolveRepoContext();
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: githubInstallationToken,
|
||||
});
|
||||
|
||||
const result = await octokit.rest.issues.createComment({
|
||||
owner: repoContext.owner,
|
||||
repo: repoContext.name,
|
||||
issue_number: issueNumber,
|
||||
body: body,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
commentId: result.data.id,
|
||||
url: result.data.html_url,
|
||||
body: result.data.body,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error creating comment: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
error: errorMessage,
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
return {
|
||||
success: true,
|
||||
commentId: result.data.id,
|
||||
url: result.data.html_url,
|
||||
body: result.data.body,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@ import { fromHere } from "@ark/fs";
|
||||
|
||||
const actionPath = fromHere("..");
|
||||
|
||||
export const mcpServerName = "gh-pullfrog";
|
||||
|
||||
export function createMcpConfig(githubInstallationToken: string) {
|
||||
const githubRepository = process.env.GITHUB_REPOSITORY;
|
||||
if (!githubRepository) {
|
||||
@@ -16,7 +18,7 @@ export function createMcpConfig(githubInstallationToken: string) {
|
||||
return JSON.stringify(
|
||||
{
|
||||
mcpServers: {
|
||||
minimal_github_comment: {
|
||||
[mcpServerName]: {
|
||||
command: "node",
|
||||
args: [`${actionPath}/mcp/server.ts`],
|
||||
env: {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { type } from "arktype";
|
||||
import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
export const Issue = type({
|
||||
title: type.string.describe("the title of the issue"),
|
||||
body: type.string.describe("the body content of the issue"),
|
||||
labels: type.string
|
||||
.array()
|
||||
.describe("optional array of label names to apply to the issue")
|
||||
.optional(),
|
||||
assignees: type.string
|
||||
.array()
|
||||
.describe("optional array of usernames to assign to the issue")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const IssueTool = tool({
|
||||
name: "create_issue",
|
||||
description: "Create a new GitHub issue",
|
||||
parameters: Issue,
|
||||
execute: contextualize(async ({ title, body, labels, assignees }, ctx) => {
|
||||
const result = await ctx.octokit.rest.issues.create({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
title: title,
|
||||
body: body,
|
||||
labels: labels ?? [],
|
||||
assignees: assignees ?? [],
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
issueId: result.data.id,
|
||||
number: result.data.number,
|
||||
url: result.data.html_url,
|
||||
title: result.data.title,
|
||||
state: result.data.state,
|
||||
labels: result.data.labels?.map((label) => (typeof label === "string" ? label : label.name)),
|
||||
assignees: result.data.assignees?.map((assignee) => assignee.login),
|
||||
};
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { type } from "arktype";
|
||||
import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
export const PullRequest = type({
|
||||
title: type.string.describe("the title of the pull request"),
|
||||
body: type.string.describe("the body content of the pull request"),
|
||||
base: type.string.describe("the base branch to merge into (e.g., 'main')"),
|
||||
});
|
||||
|
||||
export const PullRequestTool = tool({
|
||||
name: "create_pull_request",
|
||||
description: "Create a pull request from the current branch",
|
||||
parameters: PullRequest,
|
||||
execute: contextualize(async ({ title, body, base }, ctx) => {
|
||||
// Get the current branch name
|
||||
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
|
||||
console.log(`Current branch: ${currentBranch}`);
|
||||
|
||||
const result = await ctx.octokit.rest.pulls.create({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
title: title,
|
||||
body: body,
|
||||
head: currentBranch,
|
||||
base: base,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
pullRequestId: result.data.id,
|
||||
number: result.data.number,
|
||||
url: result.data.html_url,
|
||||
title: result.data.title,
|
||||
head: result.data.head.ref,
|
||||
base: result.data.base.ref,
|
||||
};
|
||||
}),
|
||||
});
|
||||
+4
-2
@@ -2,13 +2,15 @@
|
||||
// Minimal GitHub Issue Comment MCP Server
|
||||
import { FastMCP } from "fastmcp";
|
||||
import { CommentTool } from "./comment.ts";
|
||||
import { IssueTool } from "./issue.ts";
|
||||
import { PullRequestTool } from "./pr.ts";
|
||||
import { addTools } from "./shared.ts";
|
||||
|
||||
const server = new FastMCP({
|
||||
name: "Minimal GitHub Issue Comment Server",
|
||||
name: "gh-pullfrog",
|
||||
version: "0.0.1",
|
||||
});
|
||||
|
||||
addTools(server, [CommentTool]);
|
||||
addTools(server, [CommentTool, IssueTool, PullRequestTool]);
|
||||
|
||||
server.start();
|
||||
|
||||
+68
-1
@@ -1,7 +1,37 @@
|
||||
import { cached } from "@ark/util";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
||||
import type { FastMCP, Tool } from "fastmcp";
|
||||
import { parseRepoContext, type RepoContext } from "../utils/github.ts";
|
||||
|
||||
export const tool = <const params>(tool: Tool<{}, StandardSchemaV1<params>>) => tool;
|
||||
export interface ToolResult {
|
||||
content: {
|
||||
type: "text";
|
||||
text: string;
|
||||
}[];
|
||||
error?: string;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export const getMcpContext = cached((): McpContext => {
|
||||
const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
if (!githubInstallationToken) {
|
||||
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
|
||||
}
|
||||
|
||||
return {
|
||||
...parseRepoContext(),
|
||||
octokit: new Octokit({
|
||||
auth: githubInstallationToken,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
export interface McpContext extends RepoContext {
|
||||
octokit: Octokit;
|
||||
}
|
||||
|
||||
export const tool = <const params>(tool: Tool<any, StandardSchemaV1<params>>) => tool;
|
||||
|
||||
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
||||
for (const tool of tools) {
|
||||
@@ -9,3 +39,40 @@ export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
||||
}
|
||||
return server;
|
||||
};
|
||||
|
||||
export const contextualize =
|
||||
<T>(executor: (params: T, ctx: McpContext) => Promise<Record<string, any>>) =>
|
||||
async (params: T): Promise<ToolResult> => {
|
||||
try {
|
||||
const ctx = getMcpContext();
|
||||
const result = await executor(params, ctx);
|
||||
return handleToolSuccess(result);
|
||||
} catch (error) {
|
||||
return handleToolError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToolSuccess = (data: Record<string, any>): ToolResult => {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const handleToolError = (error: unknown): ToolResult => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
error: errorMessage,
|
||||
isError: true,
|
||||
};
|
||||
};
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.55",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
@@ -21,7 +21,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@ark/fs": "0.49.0",
|
||||
"@ark/fs": "0.50.0",
|
||||
"@ark/util": "0.50.0",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@octokit/webhooks-types": "^7.6.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
|
||||
Generated
+8
-5
@@ -12,8 +12,11 @@ importers:
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.1
|
||||
'@ark/fs':
|
||||
specifier: 0.49.0
|
||||
version: 0.49.0
|
||||
specifier: 0.50.0
|
||||
version: 0.50.0
|
||||
'@ark/util':
|
||||
specifier: 0.50.0
|
||||
version: 0.50.0
|
||||
'@octokit/rest':
|
||||
specifier: ^22.0.0
|
||||
version: 22.0.0
|
||||
@@ -63,8 +66,8 @@ packages:
|
||||
'@actions/io@1.1.3':
|
||||
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
|
||||
|
||||
'@ark/fs@0.49.0':
|
||||
resolution: {integrity: sha512-AEjAQS/bu1CGIRiKK/XLaQ73cSJHixfexq28wNt+kBpQ0h1RwVIVzaGsn/+5IWw6DEbR7LB+3hil5gzrzEeyZQ==}
|
||||
'@ark/fs@0.50.0':
|
||||
resolution: {integrity: sha512-6OrxNt2T+/pL4RUMZK/aiVRLIS3acNs5uSpHDyZAP6+OXwXchFSQ1lJTH+uuGBlDWeQxPD7VmymYXLF5s1Eyhw==}
|
||||
|
||||
'@ark/regex@0.0.0':
|
||||
resolution: {integrity: sha512-p4vsWnd/LRGOdGQglbwOguIVhPmCAf5UzquvnDoxqhhPWTP84wWgi1INea8MgJ4SnI2gp37f13oA4Waz9vwNYg==}
|
||||
@@ -792,7 +795,7 @@ snapshots:
|
||||
|
||||
'@actions/io@1.1.3': {}
|
||||
|
||||
'@ark/fs@0.49.0': {}
|
||||
'@ark/fs@0.50.0': {}
|
||||
|
||||
'@ark/regex@0.0.0':
|
||||
dependencies:
|
||||
|
||||
+26
-5
@@ -1,6 +1,5 @@
|
||||
import { createSign } from "node:crypto";
|
||||
import * as core from "@actions/core";
|
||||
import { resolveRepoContext } from "./repo-context.ts";
|
||||
|
||||
export interface InstallationToken {
|
||||
token: string;
|
||||
@@ -55,7 +54,7 @@ function isGitHubActionsEnvironment(): boolean {
|
||||
|
||||
async function acquireTokenViaOIDC(): Promise<string> {
|
||||
core.info("Generating OIDC token...");
|
||||
|
||||
|
||||
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||
core.info("OIDC token generated successfully");
|
||||
|
||||
@@ -208,7 +207,7 @@ const findInstallationId = async (
|
||||
};
|
||||
|
||||
async function acquireTokenViaGitHubApp(): Promise<string> {
|
||||
const repoContext = resolveRepoContext();
|
||||
const repoContext = parseRepoContext();
|
||||
|
||||
const config: GitHubAppConfig = {
|
||||
appId: process.env.GITHUB_APP_ID!,
|
||||
@@ -244,9 +243,31 @@ export async function setupGitHubInstallationToken(): Promise<string> {
|
||||
}
|
||||
|
||||
const token = await acquireNewToken();
|
||||
|
||||
|
||||
core.setSecret(token);
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = token;
|
||||
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export interface RepoContext {
|
||||
owner: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse repository context from GITHUB_REPOSITORY environment variable.
|
||||
*/
|
||||
export function parseRepoContext(): RepoContext {
|
||||
const githubRepo = process.env.GITHUB_REPOSITORY;
|
||||
if (!githubRepo) {
|
||||
throw new Error("GITHUB_REPOSITORY environment variable is required");
|
||||
}
|
||||
|
||||
const [owner, name] = githubRepo.split("/");
|
||||
if (!owner || !name) {
|
||||
throw new Error(`Invalid GITHUB_REPOSITORY format: ${githubRepo}. Expected 'owner/repo'`);
|
||||
}
|
||||
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
export interface RepoContext {
|
||||
owner: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve repository context from GITHUB_REPOSITORY environment variable.
|
||||
* Throws if not available.
|
||||
*/
|
||||
export function resolveRepoContext(): RepoContext {
|
||||
const githubRepo = process.env.GITHUB_REPOSITORY;
|
||||
if (!githubRepo) {
|
||||
throw new Error('GITHUB_REPOSITORY environment variable is required');
|
||||
}
|
||||
|
||||
const [owner, name] = githubRepo.split('/');
|
||||
if (!owner || !name) {
|
||||
throw new Error(`Invalid GITHUB_REPOSITORY format: ${githubRepo}. Expected 'owner/repo'`);
|
||||
}
|
||||
|
||||
return { owner, name };
|
||||
}
|
||||
Reference in New Issue
Block a user