main
  1/**
  2 * Repository action handlers for GitHub extension
  3 */
  4
  5import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
  6import type { GhDetails } from "../types";
  7import { parseRepo, parseReleaseList, getErrorMessage, formatRelativeDate, execGh } from "../utils";
  8
  9/**
 10 * View repository info
 11 */
 12export async function handleRepoView(
 13	pi: ExtensionAPI,
 14	params: any,
 15	signal: AbortSignal | undefined,
 16	onUpdate: any,
 17	ctx: ExtensionContext,
 18): Promise<any> {
 19	onUpdate?.({ content: [{ type: "text", text: "Fetching repo info..." }] });
 20
 21	const result = await execGh(pi, ctx,
 22		[
 23			"repo",
 24			"view",
 25			"--json",
 26			"nameWithOwner,description,defaultBranchRef,visibility,url,stargazerCount,forkCount,isArchived",
 27		],
 28		{ signal, timeout: 20000 },
 29	);
 30
 31	if (result.code !== 0) {
 32		return {
 33			content: [{ type: "text", text: getErrorMessage(result.stderr, "View repo") }],
 34			details: { action: "repo-view", error: result.stderr } as GhDetails,
 35			isError: true,
 36		};
 37	}
 38
 39	const repo = parseRepo(result.stdout);
 40	if (!repo) {
 41		return {
 42			content: [{ type: "text", text: "Could not parse repository information" }],
 43			details: { action: "repo-view", error: "parse_error" } as GhDetails,
 44			isError: true,
 45		};
 46	}
 47
 48	let output = `# ${repo.nameWithOwner}\n\n`;
 49	if (repo.description) output += `${repo.description}\n\n`;
 50	output += `Visibility: ${repo.visibility}\n`;
 51	output += `Default branch: ${repo.defaultBranch}\n`;
 52	output += `Stars: ${repo.stargazerCount} | Forks: ${repo.forkCount}\n`;
 53	output += `URL: ${repo.url}\n`;
 54	if (repo.isArchived) output += `⚠️ This repository is archived\n`;
 55
 56	return {
 57		content: [{ type: "text", text: output }],
 58		details: { action: "repo-view", output } as GhDetails,
 59	};
 60}
 61
 62/**
 63 * List releases
 64 */
 65export async function handleReleaseList(
 66	pi: ExtensionAPI,
 67	params: any,
 68	signal: AbortSignal | undefined,
 69	onUpdate: any,
 70	ctx: ExtensionContext,
 71): Promise<any> {
 72	const args = [
 73		"release",
 74		"list",
 75		"--json",
 76		"tagName,name,isDraft,isPrerelease,publishedAt",
 77	];
 78
 79	if (params.limit) args.push("--limit", String(params.limit));
 80	else args.push("--limit", "10");
 81
 82	onUpdate?.({ content: [{ type: "text", text: "Fetching releases..." }] });
 83
 84	const result = await execGh(pi, ctx, args, { signal, timeout: 20000 });
 85
 86	if (result.code !== 0) {
 87		return {
 88			content: [{ type: "text", text: getErrorMessage(result.stderr, "List releases") }],
 89			details: { action: "release-list", error: result.stderr } as GhDetails,
 90			isError: true,
 91		};
 92	}
 93
 94	const releases = parseReleaseList(result.stdout);
 95
 96	if (releases.length === 0) {
 97		return {
 98			content: [{ type: "text", text: "No releases found." }],
 99			details: { action: "release-list", output: "No releases found" } as GhDetails,
100		};
101	}
102
103	let output = `Releases (${releases.length}):\n\n`;
104	for (const release of releases) {
105		const flags: string[] = [];
106		if (release.isDraft) flags.push("draft");
107		if (release.isPrerelease) flags.push("pre-release");
108		const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
109		const date = formatRelativeDate(release.publishedAt);
110
111		output += `${release.tagName}`;
112		if (release.name && release.name !== release.tagName) output += ` - ${release.name}`;
113		output += `${flagStr}`;
114		if (date) output += ` (${date})`;
115		output += "\n";
116	}
117
118	return {
119		content: [{ type: "text", text: output }],
120		details: { action: "release-list", output } as GhDetails,
121	};
122}