Skip to content

Commit 7b043d3

Browse files
authored
Merge pull request #1 from sarth6/github-pr-conductor-extension
Build GitHub Conductor Chrome extension
2 parents 5260fba + 761993c commit 7b043d3

33 files changed

Lines changed: 8374 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
check:
14+
name: Lint, typecheck, test, build
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: '22'
22+
cache: 'npm'
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
27+
- name: Typecheck
28+
run: npm run typecheck
29+
30+
- name: Lint
31+
run: npm run lint
32+
33+
- name: Format check
34+
run: npm run format:check
35+
36+
- name: Test
37+
run: npm test
38+
39+
- name: Build
40+
run: npm run build
41+
42+
- name: Upload extension artifact
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: github-conductor-extension
46+
path: dist/
47+
retention-days: 14

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
node_modules/
2+
dist/
3+
coverage/
4+
.DS_Store
5+
*.log
6+
.env
7+
.env.*
8+
!.env.example
9+
.vite/
10+
.crx-cache/
11+
*.zip
12+
.claude/
13+
.context/

.prettierignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
dist/
3+
coverage/
4+
*.lock
5+
package-lock.json
6+
.claude/
7+
.context/

.prettierrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"semi": true,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"printWidth": 100,
6+
"tabWidth": 2,
7+
"arrowParens": "always"
8+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Sarth Frey
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# GitHub Conductor
2+
3+
> One click from any GitHub PR to a Conductor workspace with your prompt
4+
> already running.
5+
6+
A lightweight Chrome extension that injects a button on every GitHub Pull
7+
Request page. Click it and your configured prompt — populated with the PR's
8+
metadata — opens in a fresh [Conductor](https://conductor.build) workspace via
9+
the `conductor://` deep link.
10+
11+
![screenshot placeholder](public/icons/icon-128.png)
12+
13+
## Why
14+
15+
Rotating through PRs is the most repeatable part of a senior engineer's day.
16+
This extension collapses "open PR → copy URL → switch apps → paste into
17+
Conductor → write the same `gh pr view ...` boilerplate again" into a single
18+
keystroke. Every PR becomes a one-click into a working Conductor session.
19+
20+
## Install (developer mode)
21+
22+
The extension isn't on the Chrome Web Store yet. To install from source:
23+
24+
```bash
25+
git clone https://github.com/sarth6/github-conductor.git
26+
cd github-conductor
27+
npm install
28+
npm run build
29+
```
30+
31+
Then in Chrome:
32+
33+
1. Visit `chrome://extensions`
34+
2. Toggle **Developer mode** on (top right)
35+
3. Click **Load unpacked**
36+
4. Pick the `dist/` directory
37+
38+
You should see the **GitHub Conductor** icon in your toolbar. Visit any GitHub
39+
PR — a Conductor button appears in the header next to GitHub's actions.
40+
41+
## Configuration
42+
43+
Right-click the toolbar icon → **Options** (or use the gear in the popup).
44+
45+
### URL template
46+
47+
Sets the `conductor://` URL opened on click. The default is:
48+
49+
```
50+
conductor://new?prompt={prompt}
51+
```
52+
53+
`{prompt}` is replaced with the URL-encoded rendered prompt. You can also use
54+
PR metadata placeholders directly in the URL (e.g. to set the workspace path):
55+
56+
```
57+
conductor://new?prompt={prompt}&path=/Users/me/code/{repoName}
58+
```
59+
60+
### Prompt presets
61+
62+
Each preset has a **name** and a **template**. Templates use
63+
`{placeholderName}` syntax, replaced with metadata from the PR at click time.
64+
65+
Default presets that ship with the extension:
66+
67+
- **Review PR** — code-review oriented prompt
68+
- **Address PR comments** — pulls in `gh pr view --comments` workflow
69+
70+
Mark any preset as the **default** (radio button) — that's the one the inline
71+
GitHub button runs. All presets are accessible from the toolbar popup.
72+
73+
### Placeholders
74+
75+
| Placeholder | Source |
76+
| ----------------- | ----------------------- |
77+
| `{prUrl}` | Canonical PR URL |
78+
| `{prNumber}` | PR number |
79+
| `{prTitle}` | PR title |
80+
| `{prDescription}` | PR body (markdown text) |
81+
| `{prAuthor}` | Author login |
82+
| `{prBranch}` | Head branch |
83+
| `{prBaseBranch}` | Base branch |
84+
| `{repo}` | `owner/repo` |
85+
| `{repoOwner}` | `owner` |
86+
| `{repoName}` | `repo` |
87+
| `{prDiffUrl}` | `…/pull/N.diff` URL |
88+
| `{prPatchUrl}` | `…/pull/N.patch` URL |
89+
90+
## Architecture
91+
92+
Five small, separately testable modules:
93+
94+
```
95+
src/
96+
├── types.ts ← shared TypeScript types
97+
├── storage.ts ← chrome.storage.sync adapter + in-memory fallback
98+
├── template.ts ← {placeholder} substitution engine
99+
├── conductor-url.ts ← builds conductor:// URLs with safe encoding
100+
├── pr-scraper.ts ← extracts PR metadata from the DOM
101+
├── content/ ← content script: button injection on PR pages
102+
├── options/ ← settings page
103+
└── popup/ ← toolbar popup with preset list
104+
```
105+
106+
Side effects live at the boundary (`storage`, `content`, `popup`). Everything
107+
else is pure functions, which is why **34 unit tests** run in under a second.
108+
109+
## Development
110+
111+
```bash
112+
npm install
113+
npm run dev # Vite dev server with HMR for the options/popup pages
114+
npm run build # production build → dist/
115+
npm test # run all unit tests
116+
npm run typecheck # tsc --noEmit
117+
npm run lint # eslint
118+
npm run check # typecheck + lint + format-check + test
119+
```
120+
121+
### Tech stack
122+
123+
- **TypeScript** with strict mode + `exactOptionalPropertyTypes`
124+
- **Vite 6** + **@crxjs/vite-plugin** for Manifest V3 bundling and HMR
125+
- **Vitest** with **jsdom** for unit + DOM tests
126+
- **ESLint 9** (flat config) + **Prettier**
127+
- **GitHub Actions** CI: typecheck, lint, format-check, test, build
128+
129+
### Permissions
130+
131+
Minimal: only `storage` (to save your presets) and a single host permission
132+
for `*://github.com/*`. The extension does **not** read any other site, make
133+
network requests, or run on background tabs — it's a pure content script that
134+
fires on a button click.
135+
136+
## How the deep link works
137+
138+
When you click the button:
139+
140+
1. The content script reads PR metadata from the DOM (title, branches,
141+
author, etc.) and the URL (`/owner/repo/pull/N`).
142+
2. Your chosen preset template is rendered — `{placeholder}` tokens are
143+
substituted with the metadata.
144+
3. The rendered prompt is URL-encoded and dropped into your URL template's
145+
`{prompt}` slot.
146+
4. The final `conductor://` URL is fired through a hidden iframe. macOS's
147+
LaunchServices routes it to `Conductor.app`, which opens with the prompt
148+
ready to go.
149+
150+
Substitution is a plain string replace — placeholder values are **never**
151+
interpreted as templates, code, or shell input. The URL is built with
152+
`encodeURIComponent`, so PR titles containing `&`, `=`, `#`, newlines, or
153+
unicode are handled safely.
154+
155+
## License
156+
157+
MIT — see [LICENSE](LICENSE).

eslint.config.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import tseslint from '@typescript-eslint/eslint-plugin';
2+
import tsparser from '@typescript-eslint/parser';
3+
import prettier from 'eslint-config-prettier';
4+
5+
export default [
6+
{
7+
ignores: [
8+
'dist/**',
9+
'node_modules/**',
10+
'coverage/**',
11+
'*.config.js',
12+
'.claude/**',
13+
'.context/**',
14+
],
15+
},
16+
{
17+
files: ['**/*.ts'],
18+
languageOptions: {
19+
parser: tsparser,
20+
parserOptions: {
21+
ecmaVersion: 2022,
22+
sourceType: 'module',
23+
},
24+
globals: {
25+
chrome: 'readonly',
26+
window: 'readonly',
27+
document: 'readonly',
28+
console: 'readonly',
29+
URL: 'readonly',
30+
URLSearchParams: 'readonly',
31+
MutationObserver: 'readonly',
32+
HTMLElement: 'readonly',
33+
HTMLButtonElement: 'readonly',
34+
HTMLAnchorElement: 'readonly',
35+
HTMLTextAreaElement: 'readonly',
36+
HTMLInputElement: 'readonly',
37+
HTMLSelectElement: 'readonly',
38+
HTMLDivElement: 'readonly',
39+
Element: 'readonly',
40+
Event: 'readonly',
41+
CustomEvent: 'readonly',
42+
location: 'readonly',
43+
navigator: 'readonly',
44+
setTimeout: 'readonly',
45+
clearTimeout: 'readonly',
46+
Node: 'readonly',
47+
},
48+
},
49+
plugins: {
50+
'@typescript-eslint': tseslint,
51+
},
52+
rules: {
53+
...tseslint.configs.recommended.rules,
54+
'@typescript-eslint/no-unused-vars': [
55+
'error',
56+
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
57+
],
58+
'@typescript-eslint/no-explicit-any': 'error',
59+
'@typescript-eslint/consistent-type-imports': 'error',
60+
'no-console': ['warn', { allow: ['warn', 'error'] }],
61+
},
62+
},
63+
prettier,
64+
];

0 commit comments

Comments
 (0)