/** * Arcadia Suite - GitHub Integration Service (Expandido) * * Este serviço fornece funcionalidades para: * 1. Fazer commits automáticos no repositório do Arcadia Suite * 2. LER repositórios externos (como n8n, OpenManus, etc.) para análise e implementação * * @author Arcadia Development Team * @version 2.0.0 */ import { Octokit } from "@octokit/rest"; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); const ARCADIA_OWNER = process.env.GITHUB_OWNER || "JonasRodriguesPachceo"; const ARCADIA_REPO = process.env.GITHUB_REPO || "ArcadiaSuite-"; const DEFAULT_BRANCH = process.env.GITHUB_DEFAULT_BRANCH || "main"; interface FileContent { path: string; content: string; size: number; type: "file" | "dir"; } interface RepositoryStructure { owner: string; repo: string; branch: string; tree: TreeItem[]; totalFiles: number; totalDirs: number; } interface TreeItem { path: string; type: "blob" | "tree"; size?: number; } interface AnalysisResult { success: boolean; repository: string; structure?: RepositoryStructure; files?: FileContent[]; summary?: string; error?: string; } interface FileToCommit { path: string; content: string; } interface CommitResult { success: boolean; commitSha?: string; commitUrl?: string; message: string; } interface BranchResult { success: boolean; branchName?: string; message: string; } interface PullRequestResult { success: boolean; prNumber?: number; prUrl?: string; message: string; } export async function getRepositoryStructure( owner: string, repo: string, branch?: string ): Promise { if (!branch) { const { data: repoData } = await octokit.repos.get({ owner, repo }); branch = repoData.default_branch; } const { data: refData } = await octokit.git.getRef({ owner, repo, ref: `heads/${branch}`, }); const { data: commitData } = await octokit.git.getCommit({ owner, repo, commit_sha: refData.object.sha, }); const { data: treeData } = await octokit.git.getTree({ owner, repo, tree_sha: commitData.tree.sha, recursive: "true", }); const tree: TreeItem[] = treeData.tree.map((item) => ({ path: item.path || "", type: item.type as "blob" | "tree", size: item.size, })); return { owner, repo, branch, tree, totalFiles: tree.filter((t) => t.type === "blob").length, totalDirs: tree.filter((t) => t.type === "tree").length, }; } export async function readExternalFile( owner: string, repo: string, path: string, branch?: string ): Promise { try { const { data } = await octokit.repos.getContent({ owner, repo, path, ref: branch, }); if ("content" in data && data.content) { return Buffer.from(data.content, "base64").toString("utf-8"); } return null; } catch { return null; } } export async function readMultipleFiles( owner: string, repo: string, paths: string[], branch?: string ): Promise { const results: FileContent[] = []; for (const path of paths) { const content = await readExternalFile(owner, repo, path, branch); if (content !== null) { results.push({ path, content, size: content.length, type: "file", }); } } return results; } export async function listDirectory( owner: string, repo: string, dirPath: string, branch?: string ): Promise<{ name: string; path: string; type: "file" | "dir" }[]> { try { const { data } = await octokit.repos.getContent({ owner, repo, path: dirPath, ref: branch, }); if (Array.isArray(data)) { return data.map((item) => ({ name: item.name, path: item.path, type: item.type === "dir" ? "dir" : "file", })); } return []; } catch { return []; } } export async function searchFiles( owner: string, repo: string, pattern: string, branch?: string ): Promise { const structure = await getRepositoryStructure(owner, repo, branch); return structure.tree .filter((item) => item.type === "blob" && item.path.includes(pattern)) .map((item) => item.path); } export async function analyzeRepository( owner: string, repo: string, focusPaths?: string[] ): Promise { try { const structure = await getRepositoryStructure(owner, repo); let files: FileContent[] = []; if (focusPaths && focusPaths.length > 0) { for (const focusPath of focusPaths) { const matchingFiles = structure.tree .filter((item) => item.type === "blob" && item.path.startsWith(focusPath)) .slice(0, 20); for (const file of matchingFiles) { const content = await readExternalFile(owner, repo, file.path, structure.branch); if (content) { files.push({ path: file.path, content, size: content.length, type: "file", }); } } } } const summary = generateRepositorySummary(structure, files); return { success: true, repository: `${owner}/${repo}`, structure, files, summary, }; } catch (error: any) { return { success: false, repository: `${owner}/${repo}`, error: error.message, }; } } function generateRepositorySummary(structure: RepositoryStructure, files: FileContent[]): string { const lines: string[] = []; lines.push(`## Análise do Repositório: ${structure.owner}/${structure.repo}`); lines.push(`**Branch:** ${structure.branch}`); lines.push(`**Total de Arquivos:** ${structure.totalFiles}`); lines.push(`**Total de Diretórios:** ${structure.totalDirs}`); lines.push(""); const hasPackageJson = structure.tree.some((t) => t.path === "package.json"); const hasRequirements = structure.tree.some((t) => t.path === "requirements.txt"); const hasTsConfig = structure.tree.some((t) => t.path.includes("tsconfig.json")); lines.push("### Tecnologias Detectadas:"); if (hasPackageJson) lines.push("- Node.js / JavaScript"); if (hasTsConfig) lines.push("- TypeScript"); if (hasRequirements) lines.push("- Python"); lines.push(""); const topLevelDirs = Array.from(new Set(structure.tree.map((t) => t.path.split("/")[0]))).slice(0, 15); lines.push("### Estrutura Principal:"); topLevelDirs.forEach((dir) => lines.push(`- ${dir}/`)); lines.push(""); if (files.length > 0) { lines.push(`### Arquivos Analisados (${files.length}):`); files.forEach((f) => lines.push(`- ${f.path} (${f.size} bytes)`)); } return lines.join("\n"); } async function getCurrentCommit(owner: string, repo: string, branch: string) { const { data: refData } = await octokit.git.getRef({ owner, repo, ref: `heads/${branch}`, }); const commitSha = refData.object.sha; const { data: commitData } = await octokit.git.getCommit({ owner, repo, commit_sha: commitSha, }); return { commitSha, treeSha: commitData.tree.sha }; } export async function commitFiles( files: FileToCommit[], message: string, branch: string = DEFAULT_BRANCH ): Promise { try { const { commitSha, treeSha } = await getCurrentCommit(ARCADIA_OWNER, ARCADIA_REPO, branch); const blobs: { sha: string; path: string }[] = []; for (const file of files) { const { data } = await octokit.git.createBlob({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, content: file.content, encoding: "utf-8", }); blobs.push({ sha: data.sha, path: file.path }); } const tree = blobs.map(({ sha, path }) => ({ path, mode: "100644" as const, type: "blob" as const, sha, })); const { data: newTree } = await octokit.git.createTree({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, tree, base_tree: treeSha, }); const { data: newCommit } = await octokit.git.createCommit({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, message, tree: newTree.sha, parents: [commitSha], }); await octokit.git.updateRef({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, ref: `heads/${branch}`, sha: newCommit.sha, }); return { success: true, commitSha: newCommit.sha, commitUrl: `https://github.com/${ARCADIA_OWNER}/${ARCADIA_REPO}/commit/${newCommit.sha}`, message: `Commit realizado: ${message}`, }; } catch (error: any) { return { success: false, message: `Erro ao fazer commit: ${error.message}`, }; } } export async function createBranch( newBranchName: string, sourceBranch: string = DEFAULT_BRANCH ): Promise { try { const { data: refData } = await octokit.git.getRef({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, ref: `heads/${sourceBranch}`, }); await octokit.git.createRef({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, ref: `refs/heads/${newBranchName}`, sha: refData.object.sha, }); return { success: true, branchName: newBranchName, message: `Branch '${newBranchName}' criada com sucesso a partir de '${sourceBranch}'`, }; } catch (error: any) { return { success: false, message: `Erro ao criar branch: ${error.message}`, }; } } export async function createPullRequest( title: string, body: string, headBranch: string, baseBranch: string = DEFAULT_BRANCH ): Promise { try { const { data } = await octokit.pulls.create({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, title, body, head: headBranch, base: baseBranch, }); return { success: true, prNumber: data.number, prUrl: data.html_url, message: `Pull Request #${data.number} criado com sucesso`, }; } catch (error: any) { return { success: false, message: `Erro ao criar Pull Request: ${error.message}`, }; } } export async function getRepositoryInfo(): Promise<{ name: string; fullName: string; defaultBranch: string; url: string; }> { const { data } = await octokit.repos.get({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, }); return { name: data.name, fullName: data.full_name, defaultBranch: data.default_branch, url: data.html_url, }; } export async function listBranches(): Promise { const { data } = await octokit.repos.listBranches({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, }); return data.map((branch) => branch.name); } export async function getFileContent( filePath: string, branch: string = DEFAULT_BRANCH ): Promise { try { const { data } = await octokit.repos.getContent({ owner: ARCADIA_OWNER, repo: ARCADIA_REPO, path: filePath, ref: branch, }); if ("content" in data && data.content) { return Buffer.from(data.content, "base64").toString("utf-8"); } return null; } catch { return null; } } export async function toolGitHubCommit( message: string, files: { path: string; content: string }[] ): Promise<{ success: boolean; result: string }> { const result = await commitFiles(files, message); return { success: result.success, result: result.success ? `✅ Commit realizado: ${result.commitUrl}` : `❌ Erro: ${result.message}`, }; } export async function toolAnalyzeExternalRepo( repoUrl: string, focusPaths?: string[] ): Promise<{ success: boolean; result: string; data?: AnalysisResult }> { const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); if (!match) { return { success: false, result: "URL inválida. Use o formato: https://github.com/owner/repo", }; } const [, owner, repo] = match; const cleanRepo = repo.replace(/\.git$/, ""); const analysis = await analyzeRepository(owner, cleanRepo, focusPaths); return { success: analysis.success, result: analysis.success ? `✅ Repositório analisado: ${analysis.repository}\n\n${analysis.summary}` : `❌ Erro: ${analysis.error}`, data: analysis, }; } export async function toolReadExternalFile( repoUrl: string, filePath: string ): Promise<{ success: boolean; result: string; content?: string }> { const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); if (!match) { return { success: false, result: "URL inválida. Use o formato: https://github.com/owner/repo", }; } const [, owner, repo] = match; const cleanRepo = repo.replace(/\.git$/, ""); const content = await readExternalFile(owner, cleanRepo, filePath); if (content) { return { success: true, result: `✅ Arquivo lido: ${filePath} (${content.length} bytes)`, content, }; } else { return { success: false, result: `❌ Arquivo não encontrado: ${filePath}`, }; } } export default { getRepositoryStructure, readExternalFile, readMultipleFiles, listDirectory, searchFiles, analyzeRepository, commitFiles, createBranch, createPullRequest, getRepositoryInfo, listBranches, getFileContent, toolGitHubCommit, toolAnalyzeExternalRepo, toolReadExternalFile, };