|
| 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 | + |
| 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). |
0 commit comments