/** * Automatically trust checked-out repositories owned by trusted GitHub owners. * * This handles pi's project_trust event before project-local resources are * loaded. If the current directory is inside a git checkout whose origin remote * points at github.com/goofansu/* or github.int.exe.xyz/goofansu/*, trust is * granted and remembered so future sessions do not prompt. */ import type { ExtensionAPI, ProjectTrustEventResult, } from "@earendil-works/pi-coding-agent"; const TRUSTED_GITHUB_OWNERS = new Set(["goofansu"]); const GIT_TIMEOUT_MS = 5_000; type GitHubRepo = { owner: string; repo: string; }; function trimGitSuffix(repo: string): string { return repo.replace(/\.git$/i, ""); } function parseGitHubRemoteUrl(remoteUrl: string): GitHubRepo | null { const value = remoteUrl.trim(); if (!value) { return null; } // SCP-like SSH syntax: git@github.com:owner/repo.git const scpMatch = value.match( /^(?:[^@/:\s]+@)?github\.com:([^/:\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i, ); if (scpMatch) { return { owner: scpMatch[1], repo: trimGitSuffix(scpMatch[2]), }; } try { const parsed = new URL(value); if ( (parsed.protocol !== "https:" && parsed.protocol !== "ssh:") || (parsed.hostname.toLowerCase() !== "github.com" && !( parsed.protocol === "https:" && parsed.hostname.toLowerCase() === "github.int.exe.xyz" )) ) { return null; } const parts = parsed.pathname.replace(/^\/|\/$/g, "").split("/"); if (parts.length !== 2 || parts.some((part) => !part)) { return null; } return { owner: decodeURIComponent(parts[0]), repo: trimGitSuffix(decodeURIComponent(parts[1])), }; } catch { return null; } } export function isTrustedGitHubRemote(remoteUrl: string): boolean { const repo = parseGitHubRemoteUrl(remoteUrl); return !!repo && TRUSTED_GITHUB_OWNERS.has(repo.owner.toLowerCase()); } async function getOriginRemoteUrls( pi: ExtensionAPI, cwd: string, ): Promise { try { const result = await pi.exec( "git", ["remote", "get-url", "--all", "origin"], { cwd, timeout: GIT_TIMEOUT_MS }, ); if (result.code !== 0) { return []; } return result.stdout .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean); } catch { return []; } } export default function trustGitHubReposExtension(pi: ExtensionAPI): void { pi.on("project_trust", async (event): Promise => { const originUrls = await getOriginRemoteUrls(pi, event.cwd); if (originUrls.length > 0 && originUrls.every(isTrustedGitHubRemote)) { return { trusted: "yes", remember: true }; } return { trusted: "undecided" }; }); }