Files
letta-code/src/agent/github-utils.ts
2026-02-05 12:46:42 -08:00

62 lines
1.6 KiB
TypeScript

/**
* Shared GitHub API utilities for skills import/export
*/
export interface GitHubEntry {
type: "file" | "dir";
name: string;
path: string;
download_url?: string;
}
/**
* Fetch GitHub contents using gh CLI (authenticated) or direct API
* Returns array of directory/file entries
*/
export async function fetchGitHubContents(
owner: string,
repo: string,
branch: string,
path: string,
): Promise<GitHubEntry[]> {
const apiPath = path
? `repos/${owner}/${repo}/contents/${path}?ref=${branch}`
: `repos/${owner}/${repo}/contents?ref=${branch}`;
// Try gh CLI (authenticated, 5000 req/hr)
try {
const { execSync } = await import("node:child_process");
const result = execSync(`gh api ${apiPath}`, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "ignore"],
});
return JSON.parse(result) as GitHubEntry[];
} catch {
// Fall back to unauthenticated API (60 req/hr)
}
// Try direct API
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "letta-code",
},
});
if (!response.ok) {
throw new Error(
`Failed to fetch from ${owner}/${repo}/${branch}/${path}: ${response.statusText}`,
);
}
return (await response.json()) as GitHubEntry[];
}
/**
* Extract directory names from GitHub entries
*/
export function parseDirNames(entries: GitHubEntry[]): Set<string> {
return new Set(entries.filter((e) => e.type === "dir").map((e) => e.name));
}