From 873cafa46f4ad5eb090f9555735b4775c67a0f00 Mon Sep 17 00:00:00 2001 From: Amen-Ra Mendel Date: Fri, 19 Dec 2025 05:22:41 -0500 Subject: [PATCH] Fix GitHub organization repository support ## Summary - Add support for organization repositories by including org members in repo list API - Add repository reference normalization to handle full GitHub URLs, SSH URLs, and owner/repo format - Apply normalization to all GitHub API calls to ensure consistent behavior ## Changes Made ### 1. Enhanced repo listing (repository-handlers.ts) - Updated `/user/repos` endpoint to include affiliation parameter - Now fetches: owner, collaborator, and organization_member repos - Fixes #20: Organization repos now appear in repository list ### 2. Added URL normalization utility (utils.ts) - New `normalizeRepoReference()` function handles: - owner/repo format (already normalized) - https://github.com/owner/repo URLs - https://github.com/owner/repo.git URLs - git@github.com:owner/repo.git SSH URLs - Prevents 404 errors from malformed repository references ### 3. Applied normalization consistently - Updated repository-handlers.ts to normalize repo refs before API calls - Updated issue-handlers.ts to normalize repo refs before API calls - All GitHub API calls now use normalized repository format ## Testing - Verified with organization repository: imaginationeverywhere/ppsv-charities - Tested with various URL formats - GitHub CLI authentication continues to work seamlessly ## Related Issues Fixes #20: Auto Claude doesn't work with GitHub Organization repositories --- .../ipc-handlers/github/issue-handlers.ts | 36 +++++++++++++++---- .../github/repository-handlers.ts | 24 ++++++++++--- .../src/main/ipc-handlers/github/utils.ts | 26 ++++++++++++++ 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/auto-claude-ui/src/main/ipc-handlers/github/issue-handlers.ts b/auto-claude-ui/src/main/ipc-handlers/github/issue-handlers.ts index 2fee6693..961f97c7 100644 --- a/auto-claude-ui/src/main/ipc-handlers/github/issue-handlers.ts +++ b/auto-claude-ui/src/main/ipc-handlers/github/issue-handlers.ts @@ -6,7 +6,7 @@ import { ipcMain } from 'electron'; import { IPC_CHANNELS } from '../../../shared/constants'; import type { IPCResult, GitHubIssue } from '../../../shared/types'; import { projectStore } from '../../project-store'; -import { getGitHubConfig, githubFetch } from './utils'; +import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils'; import type { GitHubAPIIssue, GitHubAPIComment } from './types'; /** @@ -57,16 +57,24 @@ export function registerGetIssues(): void { } try { + const normalizedRepo = normalizeRepoReference(config.repo); + if (!normalizedRepo) { + return { + success: false, + error: 'Invalid repository format. Use owner/repo or GitHub URL.' + }; + } + const issues = await githubFetch( config.token, - `/repos/${config.repo}/issues?state=${state}&per_page=100&sort=updated` + `/repos/${normalizedRepo}/issues?state=${state}&per_page=100&sort=updated` ) as GitHubAPIIssue[]; // Filter out pull requests const issuesOnly = issues.filter(issue => !issue.pull_request); const result: GitHubIssue[] = issuesOnly.map(issue => - transformIssue(issue, config.repo) + transformIssue(issue, normalizedRepo) ); return { success: true, data: result }; @@ -98,12 +106,20 @@ export function registerGetIssue(): void { } try { + const normalizedRepo = normalizeRepoReference(config.repo); + if (!normalizedRepo) { + return { + success: false, + error: 'Invalid repository format. Use owner/repo or GitHub URL.' + }; + } + const issue = await githubFetch( config.token, - `/repos/${config.repo}/issues/${issueNumber}` + `/repos/${normalizedRepo}/issues/${issueNumber}` ) as GitHubAPIIssue; - const result = transformIssue(issue, config.repo); + const result = transformIssue(issue, normalizedRepo); return { success: true, data: result }; } catch (error) { @@ -134,9 +150,17 @@ export function registerGetIssueComments(): void { } try { + const normalizedRepo = normalizeRepoReference(config.repo); + if (!normalizedRepo) { + return { + success: false, + error: 'Invalid repository format. Use owner/repo or GitHub URL.' + }; + } + const comments = await githubFetch( config.token, - `/repos/${config.repo}/issues/${issueNumber}/comments` + `/repos/${normalizedRepo}/issues/${issueNumber}/comments` ) as GitHubAPIComment[]; return { success: true, data: comments }; diff --git a/auto-claude-ui/src/main/ipc-handlers/github/repository-handlers.ts b/auto-claude-ui/src/main/ipc-handlers/github/repository-handlers.ts index 0987edd5..b031a3c4 100644 --- a/auto-claude-ui/src/main/ipc-handlers/github/repository-handlers.ts +++ b/auto-claude-ui/src/main/ipc-handlers/github/repository-handlers.ts @@ -6,7 +6,7 @@ import { ipcMain } from 'electron'; import { IPC_CHANNELS } from '../../../shared/constants'; import type { IPCResult, GitHubRepository, GitHubSyncStatus } from '../../../shared/types'; import { projectStore } from '../../project-store'; -import { getGitHubConfig, githubFetch } from './utils'; +import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils'; import type { GitHubAPIRepository } from './types'; /** @@ -33,16 +33,28 @@ export function registerCheckConnection(): void { } try { + // Normalize repo reference (handles full URLs, git URLs, etc.) + const normalizedRepo = normalizeRepoReference(config.repo); + if (!normalizedRepo) { + return { + success: true, + data: { + connected: false, + error: 'Invalid repository format. Use owner/repo or GitHub URL.' + } + }; + } + // Fetch repo info const repoData = await githubFetch( config.token, - `/repos/${config.repo}` + `/repos/${normalizedRepo}` ) as { full_name: string; description?: string }; // Count open issues const issuesData = await githubFetch( config.token, - `/repos/${config.repo}/issues?state=open&per_page=1` + `/repos/${normalizedRepo}/issues?state=open&per_page=1` ) as unknown[]; const openCount = Array.isArray(issuesData) ? issuesData.length : 0; @@ -71,7 +83,7 @@ export function registerCheckConnection(): void { } /** - * Get list of GitHub repositories + * Get list of GitHub repositories (personal + organization) */ export function registerGetRepositories(): void { ipcMain.handle( @@ -88,9 +100,11 @@ export function registerGetRepositories(): void { } try { + // Fetch user's personal + organization repos + // affiliation parameter includes: owner, collaborator, organization_member const repos = await githubFetch( config.token, - '/user/repos?per_page=100&sort=updated' + '/user/repos?per_page=100&sort=updated&affiliation=owner,collaborator,organization_member' ) as GitHubAPIRepository[]; const result: GitHubRepository[] = repos.map(repo => ({ diff --git a/auto-claude-ui/src/main/ipc-handlers/github/utils.ts b/auto-claude-ui/src/main/ipc-handlers/github/utils.ts index d25da8cb..0fb4461e 100644 --- a/auto-claude-ui/src/main/ipc-handlers/github/utils.ts +++ b/auto-claude-ui/src/main/ipc-handlers/github/utils.ts @@ -54,6 +54,32 @@ export function getGitHubConfig(project: Project): GitHubConfig | null { } } +/** + * Normalize a GitHub repository reference to owner/repo format + * Handles: + * - owner/repo (already normalized) + * - https://github.com/owner/repo + * - https://github.com/owner/repo.git + * - git@github.com:owner/repo.git + */ +export function normalizeRepoReference(repo: string): string { + if (!repo) return ''; + + // Remove trailing .git if present + let normalized = repo.replace(/\.git$/, ''); + + // Handle full GitHub URLs + if (normalized.startsWith('https://github.com/')) { + normalized = normalized.replace('https://github.com/', ''); + } else if (normalized.startsWith('http://github.com/')) { + normalized = normalized.replace('http://github.com/', ''); + } else if (normalized.startsWith('git@github.com:')) { + normalized = normalized.replace('git@github.com:', ''); + } + + return normalized.trim(); +} + /** * Make a request to the GitHub API */