@ryan_nookpi/pi-extension-open-pr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +24 -0
  2. package/index.ts +140 -0
  3. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # open-pr
2
+
3
+ Open the current branch's pull request in your browser via [GitHub CLI](https://cli.github.com/).
4
+
5
+ ## What it does
6
+
7
+ Registers a `/open-pr` command that:
8
+
9
+ 1. Detects the current git branch
10
+ 2. Looks up the associated PR using `gh pr view --json url`
11
+ 3. Opens the PR URL in your default browser
12
+
13
+ Works on macOS (`open`), Linux (`xdg-open`), and Windows (`cmd /c start`).
14
+
15
+ ## Prerequisites
16
+
17
+ - [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated
18
+ - A git repository with a remote
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pi install npm:@ryan_nookpi/pi-extension-open-pr
24
+ ```
package/index.ts ADDED
@@ -0,0 +1,140 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+
3
+ type NotifyLevel = "info" | "warning" | "error";
4
+
5
+ interface BrowserCommand {
6
+ command: string;
7
+ args: string[];
8
+ }
9
+
10
+ export function parsePrViewUrl(stdout: string): string | null {
11
+ try {
12
+ const parsed = JSON.parse(stdout) as { url?: unknown };
13
+ return typeof parsed.url === "string" && parsed.url.trim().length > 0 ? parsed.url.trim() : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export function resolveBrowserOpenCommand(targetPlatform: NodeJS.Platform, url: string): BrowserCommand {
20
+ if (targetPlatform === "darwin") {
21
+ return { command: "open", args: [url] };
22
+ }
23
+ if (targetPlatform === "win32") {
24
+ return { command: "cmd", args: ["/c", "start", "", url] };
25
+ }
26
+ return { command: "xdg-open", args: [url] };
27
+ }
28
+
29
+ export function formatPrLookupError(error: string, branch: string | null): string {
30
+ const detail = error.trim() || "Unknown gh error";
31
+ const lowerDetail = detail.toLowerCase();
32
+
33
+ if (lowerDetail.includes("no pull requests found")) {
34
+ return `No pull request found for the current branch${branch ? ` (${branch})` : ""}.`;
35
+ }
36
+
37
+ if (
38
+ lowerDetail.includes("not logged into any github hosts") ||
39
+ lowerDetail.includes("authentication failed") ||
40
+ lowerDetail.includes("gh auth login") ||
41
+ lowerDetail.includes("authentication required")
42
+ ) {
43
+ return `GitHub CLI authentication failed: ${detail}`;
44
+ }
45
+
46
+ if (
47
+ lowerDetail.includes("could not resolve to a repository") ||
48
+ lowerDetail.includes("failed to determine base repo") ||
49
+ lowerDetail.includes("no git remotes found") ||
50
+ lowerDetail.includes("not a git repository")
51
+ ) {
52
+ return `GitHub repository lookup failed: ${detail}`;
53
+ }
54
+
55
+ return `Failed to resolve pull request${branch ? ` for ${branch}` : ""}: ${detail}`;
56
+ }
57
+
58
+ function notify(ctx: ExtensionContext, message: string, type: NotifyLevel): void {
59
+ if (ctx.hasUI) {
60
+ ctx.ui.notify(message, type);
61
+ return;
62
+ }
63
+
64
+ if (type === "error") {
65
+ // biome-ignore lint/suspicious/noConsole: non-UI command fallback needs terminal output.
66
+ console.error(message);
67
+ return;
68
+ }
69
+
70
+ // biome-ignore lint/suspicious/noConsole: non-UI command fallback needs terminal output.
71
+ console.log(message);
72
+ }
73
+
74
+ async function gitRoot(pi: ExtensionAPI, cwd: string): Promise<string | null> {
75
+ const result = await pi.exec("git", ["rev-parse", "--show-toplevel"], { cwd });
76
+ if (result.code !== 0) return null;
77
+ const root = (result.stdout ?? "").trim();
78
+ return root || null;
79
+ }
80
+
81
+ async function currentBranch(pi: ExtensionAPI, cwd: string): Promise<{ branch: string | null; error: string | null }> {
82
+ const result = await pi.exec("git", ["branch", "--show-current"], { cwd });
83
+ if (result.code !== 0) {
84
+ const detail = (result.stderr ?? result.stdout ?? "").trim() || "Unknown git error";
85
+ return { branch: null, error: `Failed to determine current branch: ${detail}` };
86
+ }
87
+
88
+ const branch = (result.stdout ?? "").trim();
89
+ if (!branch) {
90
+ return { branch: null, error: "No current branch detected (detached HEAD)." };
91
+ }
92
+
93
+ return { branch, error: null };
94
+ }
95
+
96
+ export function createOpenPrHandler(pi: ExtensionAPI) {
97
+ return async (_args: string, ctx: ExtensionContext) => {
98
+ const root = await gitRoot(pi, ctx.cwd);
99
+ if (!root) {
100
+ notify(ctx, "Not a git repository.", "error");
101
+ return;
102
+ }
103
+
104
+ const branchResult = await currentBranch(pi, root);
105
+ if (!branchResult.branch) {
106
+ notify(ctx, branchResult.error ?? "Failed to determine current branch.", "error");
107
+ return;
108
+ }
109
+
110
+ const prResult = await pi.exec("gh", ["pr", "view", "--json", "url"], { cwd: root });
111
+ if (prResult.code !== 0) {
112
+ const detail = (prResult.stderr ?? prResult.stdout ?? "").trim() || "Unknown gh error";
113
+ notify(ctx, formatPrLookupError(detail, branchResult.branch), "error");
114
+ return;
115
+ }
116
+
117
+ const url = parsePrViewUrl(prResult.stdout ?? "");
118
+ if (!url) {
119
+ notify(ctx, "Failed to parse PR URL from `gh pr view --json url`.", "error");
120
+ return;
121
+ }
122
+
123
+ const browser = resolveBrowserOpenCommand(process.platform, url);
124
+ const openResult = await pi.exec(browser.command, browser.args, { cwd: root });
125
+ if (openResult.code !== 0) {
126
+ const detail = (openResult.stderr ?? openResult.stdout ?? "").trim() || "Unknown browser error";
127
+ notify(ctx, `Failed to open browser: ${detail}`, "error");
128
+ return;
129
+ }
130
+
131
+ notify(ctx, `Opened PR for ${branchResult.branch}: ${url}`, "info");
132
+ };
133
+ }
134
+
135
+ export default function openPrExtension(pi: ExtensionAPI) {
136
+ pi.registerCommand("open-pr", {
137
+ description: "Open the current branch pull request in your browser via GitHub CLI",
138
+ handler: createOpenPrHandler(pi),
139
+ });
140
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@ryan_nookpi/pi-extension-open-pr",
3
+ "version": "0.1.0",
4
+ "description": "Open the current branch pull request in your browser via GitHub CLI.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Jonghakseo/pi-extension.git",
9
+ "directory": "packages/open-pr"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Jonghakseo/pi-extension/issues"
13
+ },
14
+ "homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/open-pr#readme",
15
+ "type": "module",
16
+ "keywords": [
17
+ "pi-package"
18
+ ],
19
+ "files": [
20
+ "index.ts",
21
+ "README.md"
22
+ ],
23
+ "pi": {
24
+ "extensions": [
25
+ "./index.ts"
26
+ ]
27
+ },
28
+ "peerDependencies": {
29
+ "@mariozechner/pi-coding-agent": "*"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }