use GITHUB_REPOSITORY for context

This commit is contained in:
ssalbdivad
2025-10-09 17:04:03 -04:00
parent 9459803aaa
commit 3e7122611c
7 changed files with 381 additions and 398 deletions
+6 -12
View File
@@ -86,20 +86,14 @@ export class ClaudeAgent implements Agent {
"bypassPermissions", "bypassPermissions",
]; ];
if ( if (!process.env.GITHUB_INSTALLATION_TOKEN) {
process.env.GITHUB_INSTALLATION_TOKEN && throw new Error("GITHUB_INSTALLATION_TOKEN is required for GitHub integration");
process.env.REPO_OWNER &&
process.env.REPO_NAME
) {
const mcpConfig = createMcpConfig(
process.env.GITHUB_INSTALLATION_TOKEN,
process.env.REPO_OWNER,
process.env.REPO_NAME
);
console.log("📋 MCP Config:", mcpConfig);
args.push("--mcp-config", mcpConfig);
} }
const mcpConfig = createMcpConfig(process.env.GITHUB_INSTALLATION_TOKEN);
console.log("📋 MCP Config:", mcpConfig);
args.push("--mcp-config", mcpConfig);
const env = { const env = {
ANTHROPIC_API_KEY: this.apiKey, ANTHROPIC_API_KEY: this.apiKey,
}; };
+323 -328
View File
File diff suppressed because one or more lines are too long
+1 -7
View File
@@ -3,11 +3,7 @@
*/ */
const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd(); const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd();
export function createMcpConfig( export function createMcpConfig(githubInstallationToken: string) {
githubInstallationToken: string,
repoOwner: string,
repoName: string
) {
return JSON.stringify( return JSON.stringify(
{ {
mcpServers: { mcpServers: {
@@ -16,8 +12,6 @@ export function createMcpConfig(
args: [`${actionPath}/mcp/server.ts`], args: [`${actionPath}/mcp/server.ts`],
env: { env: {
GITHUB_INSTALLATION_TOKEN: githubInstallationToken, GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
REPO_OWNER: repoOwner,
REPO_NAME: repoName,
}, },
}, },
}, },
+6 -11
View File
@@ -5,15 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { type } from "arktype"; import { type } from "arktype";
import { z } from "zod"; import { z } from "zod";
import { resolveRepoContext } from "../utils/repo-context.ts";
// Get repository information from environment variables
const REPO_OWNER = process.env.REPO_OWNER;
const REPO_NAME = process.env.REPO_NAME;
if (!REPO_OWNER || !REPO_NAME) {
console.error("Error: REPO_OWNER and REPO_NAME environment variables are required");
process.exit(1);
}
const server = new McpServer({ const server = new McpServer({
name: "Minimal GitHub Issue Comment Server", name: "Minimal GitHub Issue Comment Server",
@@ -42,13 +34,16 @@ server.tool(
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required"); throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
} }
// Resolve repository context from environment
const repoContext = resolveRepoContext();
const octokit = new Octokit({ const octokit = new Octokit({
auth: githubInstallationToken, auth: githubInstallationToken,
}); });
const result = await octokit.rest.issues.createComment({ const result = await octokit.rest.issues.createComment({
owner: REPO_OWNER, owner: repoContext.owner,
repo: REPO_NAME, repo: repoContext.name,
issue_number: issueNumber, issue_number: issueNumber,
body: body, body: body,
}); });
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@pullfrog/action", "name": "@pullfrog/action",
"version": "0.0.13", "version": "0.0.14",
"type": "module", "type": "module",
"files": [ "files": [
"index.js", "index.js",
@@ -23,7 +23,8 @@
"build:dev": "node esbuild.config.js", "build:dev": "node esbuild.config.js",
"prepare": "husky", "prepare": "husky",
"play": "node play.ts", "play": "node play.ts",
"upDeps": "pnpm up --latest" "upDeps": "pnpm up --latest",
"createLockfile": "pnpm --ignore-workspace install"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
+20 -38
View File
@@ -1,5 +1,6 @@
import { createSign } from "node:crypto"; import { createSign } from "node:crypto";
import { config } from "dotenv"; import { config } from "dotenv";
import { resolveRepoContext } from "./repo-context.ts";
config(); config();
@@ -34,26 +35,6 @@ interface RepositoriesResponse {
repositories: Repository[]; repositories: Repository[];
} }
const validateConfig = (config: GitHubAppConfig): void => {
const { appId, privateKey, repoOwner, repoName } = config;
if (!appId) {
throw new Error("GITHUB_APP_ID environment variable is required");
}
if (!privateKey) {
throw new Error("GITHUB_PRIVATE_KEY environment variable is required");
}
if (!repoOwner || !repoName) {
throw new Error("REPO_OWNER and REPO_NAME environment variables are required");
}
if (!privateKey.includes("BEGIN") || !privateKey.includes("END")) {
throw new Error("GITHUB_PRIVATE_KEY must be in PEM format");
}
};
const base64UrlEncode = (str: string): string => { const base64UrlEncode = (str: string): string => {
return Buffer.from(str) return Buffer.from(str)
.toString("base64") .toString("base64")
@@ -122,14 +103,15 @@ const githubRequest = async <T>(
return response.json() as T; return response.json() as T;
}; };
const checkRepositoryAccess = async (token: string, repoOwner: string, repoName: string): Promise<boolean> => { const checkRepositoryAccess = async (
token: string,
repoOwner: string,
repoName: string
): Promise<boolean> => {
try { try {
const response = await githubRequest<RepositoriesResponse>( const response = await githubRequest<RepositoriesResponse>("/installation/repositories", {
"/installation/repositories", headers: { Authorization: `token ${token}` },
{ });
headers: { Authorization: `token ${token}` },
}
);
return response.repositories.some( return response.repositories.some(
(repo) => repo.owner.login === repoOwner && repo.name === repoName (repo) => repo.owner.login === repoOwner && repo.name === repoName
@@ -151,7 +133,11 @@ const createInstallationToken = async (jwt: string, installationId: number): Pro
return response.token; return response.token;
}; };
const findInstallationId = async (jwt: string, repoOwner: string, repoName: string): Promise<number> => { const findInstallationId = async (
jwt: string,
repoOwner: string,
repoName: string
): Promise<number> => {
const installations = await githubRequest<Installation[]>("/app/installations", { const installations = await githubRequest<Installation[]>("/app/installations", {
headers: { Authorization: `Bearer ${jwt}` }, headers: { Authorization: `Bearer ${jwt}` },
}); });
@@ -164,8 +150,7 @@ const findInstallationId = async (jwt: string, repoOwner: string, repoName: stri
if (hasAccess) { if (hasAccess) {
return installation.id; return installation.id;
} }
} catch { } catch {}
}
} }
throw new Error( throw new Error(
@@ -174,19 +159,16 @@ const findInstallationId = async (jwt: string, repoOwner: string, repoName: stri
); );
}; };
export const generateInstallationToken = async ( export const generateInstallationToken = async (): Promise<string> => {
repoOwner?: string, const repoContext = resolveRepoContext();
repoName?: string
): Promise<string> => {
const config: GitHubAppConfig = { const config: GitHubAppConfig = {
appId: process.env.GITHUB_APP_ID!, appId: process.env.GITHUB_APP_ID!,
privateKey: process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n")!, privateKey: process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n")!,
repoOwner: repoOwner || process.env.REPO_OWNER || "pullfrogai", repoOwner: repoContext.owner,
repoName: repoName || process.env.REPO_NAME || "scratch", repoName: repoContext.name,
}; };
validateConfig(config);
const jwt = generateJWT(config.appId, config.privateKey); const jwt = generateJWT(config.appId, config.privateKey);
const installationId = await findInstallationId(jwt, config.repoOwner, config.repoName); const installationId = await findInstallationId(jwt, config.repoOwner, config.repoName);
const token = await createInstallationToken(jwt, installationId); const token = await createInstallationToken(jwt, installationId);
+22
View File
@@ -0,0 +1,22 @@
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 };
}