use GITHUB_REPOSITORY for context
This commit is contained in:
+6
-12
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-7
@@ -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
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user