Skip to content

Commit 72096cc

Browse files
committed
feat: Add GitHub token support for reviewing private repositories.
1 parent 65cc85f commit 72096cc

8 files changed

Lines changed: 221 additions & 11 deletions

File tree

npx/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ export GEMINI_API_KEY=your_key
3535
npx asyncreview review --url <url> -q "Review this"
3636
```
3737

38+
## GitHub Token (For Private Repos)
39+
40+
For private repositories, you'll need to provide a GitHub token. The token can be provided in three ways (in order of priority):
41+
42+
1. `--github-token <token>` flag
43+
2. `GITHUB_TOKEN` environment variable
44+
3. Interactive prompt (optional, if neither above is set)
45+
46+
```bash
47+
# Using --github-token flag
48+
npx asyncreview review --url <url> -q "Review this" --github-token YOUR_GITHUB_TOKEN
49+
50+
# Using environment variable
51+
export GITHUB_TOKEN=your_token
52+
npx asyncreview review --url <url> -q "Review this"
53+
```
54+
3855
## Options
3956

4057
| Option | Description |
@@ -45,6 +62,7 @@ npx asyncreview review --url <url> -q "Review this"
4562
| `--quiet` | Suppress progress output |
4663
| `-m, --model <model>` | Model to use (default: gemini-3-pro-preview) |
4764
| `--api <key>` | Gemini API key |
65+
| `--github-token <token>` | GitHub token for private repos |
4866

4967
## Examples
5068

@@ -60,6 +78,9 @@ npx asyncreview review -u https://github.com/org/repo/pull/123 -q "Document thes
6078

6179
# Scripting with JSON
6280
npx asyncreview review -u https://github.com/org/repo/pull/123 -q "Review" --quiet -o json | jq .answer
81+
82+
# Private repository review
83+
npx asyncreview review -u https://github.com/org/private-repo/pull/456 -q "Review this" --github-token ghp_xxxxxxxxxxxx
6384
```
6485

6586
## License

npx/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npx/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "asyncreview",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "AI-powered GitHub PR/Issue reviews from the command line",
55
"type": "module",
66
"bin": {

npx/src/api-key.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* API key management - handles env vars, CLI flags, and interactive prompts
2+
* API key and GitHub token management - handles env vars, CLI flags, and interactive prompts
33
*/
44

55
import inquirer from 'inquirer';
@@ -40,3 +40,39 @@ export async function getApiKey(cliApiKey?: string): Promise<string> {
4040

4141
return answers.apiKey;
4242
}
43+
44+
export async function getGitHubToken(cliToken?: string, requireToken: boolean = false): Promise<string> {
45+
// 1. Check --github-token flag first (highest priority)
46+
if (cliToken) {
47+
return cliToken;
48+
}
49+
50+
// 2. Check environment variable
51+
const envToken = process.env.GITHUB_TOKEN;
52+
if (envToken) {
53+
return envToken;
54+
}
55+
56+
// 3. If not required, return empty string (for public repos)
57+
if (!requireToken) {
58+
return '';
59+
}
60+
61+
// 4. No token found but required - prompt user
62+
console.log(chalk.yellow('\n⚠️ No GitHub token found.\n'));
63+
console.log(chalk.dim('A GitHub token is required for private repositories.'));
64+
console.log(chalk.dim('You can set it via:'));
65+
console.log(chalk.dim(' • --github-token <token> flag'));
66+
console.log(chalk.dim(' • GITHUB_TOKEN environment variable\n'));
67+
68+
const answers = await inquirer.prompt([
69+
{
70+
type: 'password',
71+
name: 'githubToken',
72+
message: 'Enter your GitHub token (or press Enter to skip):',
73+
mask: '•',
74+
},
75+
]);
76+
77+
return answers.githubToken || '';
78+
}

npx/src/cli.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import chalk from 'chalk';
66
import ora from 'ora';
7-
import { getApiKey } from './api-key.js';
7+
import { getApiKey, getGitHubToken } from './api-key.js';
88
import {
99
checkPython,
1010
checkAsyncReviewInstalled,
@@ -19,10 +19,11 @@ export interface ReviewOptions {
1919
quiet?: boolean;
2020
model?: string;
2121
api?: string;
22+
githubToken?: string;
2223
}
2324

2425
export async function runReview(options: ReviewOptions): Promise<void> {
25-
const { url, question, output, quiet = false, model, api } = options;
26+
const { url, question, output, quiet = false, model, api, githubToken } = options;
2627

2728
try {
2829
// 1. Check Python availability
@@ -63,7 +64,10 @@ export async function runReview(options: ReviewOptions): Promise<void> {
6364
// 3. Get API key
6465
const apiKey = await getApiKey(api);
6566

66-
// 4. Run the review
67+
// 4. Get GitHub token (optional for public repos)
68+
const ghToken = await getGitHubToken(githubToken, false);
69+
70+
// 5. Run the review
6771
if (!quiet) {
6872
console.log(chalk.cyan(`\n🔍 Reviewing: ${url}`));
6973
console.log(chalk.dim(` Question: ${question}\n`));
@@ -76,6 +80,7 @@ export async function runReview(options: ReviewOptions): Promise<void> {
7680
quiet,
7781
model,
7882
apiKey,
83+
githubToken: ghToken,
7984
});
8085

8186
// In quiet mode, we suppressed stdout during execution

npx/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ program
2323
.option('--quiet', 'Suppress progress output')
2424
.option('-m, --model <model>', 'Model to use (e.g. gemini-3-pro-preview)')
2525
.option('--api <key>', 'Gemini API key (defaults to GEMINI_API_KEY env var)')
26+
.option('--github-token <token>', 'GitHub token for private repos (defaults to GITHUB_TOKEN env var)')
2627
.action(async (options) => {
2728
await runReview(options);
2829
});

npx/src/python-runner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export interface RunOptions {
158158
quiet: boolean;
159159
model?: string;
160160
apiKey: string;
161+
githubToken?: string;
161162
}
162163

163164
/**
@@ -205,6 +206,7 @@ export async function runPythonReview(options: RunOptions): Promise<string> {
205206
env: {
206207
...process.env,
207208
GEMINI_API_KEY: options.apiKey,
209+
...(options.githubToken && { GITHUB_TOKEN: options.githubToken }),
208210
PYTHONPATH: pythonPath,
209211
// Ensure we don't inherit conflicting python env vars
210212
VIRTUAL_ENV: path.dirname(path.dirname(pythonCmd))

skills/asyncreview/SKILL.md

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ Use this skill when the user:
1919
## How to use this skill
2020

2121
1. **Check prerequisites** — Verify `GEMINI_API_KEY` is set
22-
2. **Get the PR/Issue URL** — Ask user if not provided
23-
3. **Formulate a question** — Convert user's request into a specific question
24-
4. **Run the review command** — Execute `npx asyncreview review --url <URL> -q "<question>"`
25-
5. **Present the results** — Share the AI's findings with sources
22+
2. **Check if repo is private** — Use `gh repo view` to determine if `GITHUB_TOKEN` is required
23+
3. **Set GITHUB_TOKEN if needed** — Use `gh auth token` for private repos
24+
4. **Get the PR/Issue URL** — Ask user if not provided
25+
5. **Formulate a question** — Convert user's request into a specific question
26+
6. **Run the review command** — Execute `npx asyncreview review --url <URL> -q "<question>"`
27+
7. **Present the results** — Share the AI's findings with sources
2628

2729
## Prerequisites
2830

31+
### 1. Check for `GEMINI_API_KEY` (Required)
32+
2933
**Before running any command, check for `GEMINI_API_KEY`:**
3034

3135
```bash
@@ -40,6 +44,103 @@ Then set it:
4044
export GEMINI_API_KEY="user-provided-key"
4145
```
4246

47+
### 2. Check if Repository is Private (Critical)
48+
49+
**IMPORTANT:** Before reviewing a PR/Issue, you MUST check if the repository is private. If it is, `GITHUB_TOKEN` is **REQUIRED**.
50+
51+
#### Step 1: Extract owner and repo from URL
52+
53+
From a URL like `https://github.com/owner/repo/pull/123`, extract:
54+
- owner: `owner`
55+
- repo: `repo`
56+
57+
#### Step 2: Check repository visibility
58+
59+
**Option A: Using GitHub CLI (if available)**
60+
61+
```bash
62+
gh repo view owner/repo --json isPrivate -q '.isPrivate'
63+
```
64+
65+
**Option B: Without GitHub CLI (using curl)**
66+
67+
```bash
68+
# Try to access the repo via GitHub API without authentication
69+
curl -s -o /dev/null -w "%{http_code}" https://api.github.com/repos/owner/repo
70+
```
71+
72+
**Possible outcomes:**
73+
- **With `gh`:**
74+
- `true` → Repository is **private**, `GITHUB_TOKEN` is **REQUIRED**
75+
- `false` → Repository is **public**, `GITHUB_TOKEN` is **optional**
76+
- Error (e.g., "not found") → May indicate private repo without auth, or repo doesn't exist
77+
78+
- **With `curl`:**
79+
- `200` → Repository is **public**, `GITHUB_TOKEN` is **optional** (but recommended for higher rate limits)
80+
- `404` → Repository is **private** or doesn't exist, `GITHUB_TOKEN` is **REQUIRED**
81+
- `403` → Rate limited, need `GITHUB_TOKEN`
82+
83+
#### Step 3: If private, ensure `GITHUB_TOKEN` is set
84+
85+
```bash
86+
# Check if GITHUB_TOKEN is already set
87+
echo $GITHUB_TOKEN
88+
```
89+
90+
If empty or not set, obtain it using one of these methods:
91+
92+
**Option A: Using GitHub CLI (if available)**
93+
94+
```bash
95+
# Get token from GitHub CLI (must be authenticated with `gh auth login` first)
96+
export GITHUB_TOKEN=$(gh auth token)
97+
98+
# Verify it's set
99+
echo $GITHUB_TOKEN
100+
```
101+
102+
If `gh auth token` fails, authenticate first:
103+
104+
```bash
105+
gh auth login
106+
```
107+
108+
**Option B: Without GitHub CLI (create token via web)**
109+
110+
1. Go to GitHub: https://github.com/settings/tokens
111+
2. Click **"Generate new token"****"Generate new token (classic)"**
112+
3. Give it a descriptive name (e.g., "AsyncReview CLI")
113+
4. Select scopes:
114+
-`repo` (Full control of private repositories)
115+
5. Click **"Generate token"**
116+
6. Copy the token (you won't see it again!)
117+
7. Set it in your terminal:
118+
119+
```bash
120+
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
121+
122+
# Verify it's set
123+
echo $GITHUB_TOKEN
124+
```
125+
126+
**Security tip:** For better security, consider creating a fine-grained token with minimal permissions:
127+
- Go to: https://github.com/settings/personal-access-tokens/new
128+
- Select specific repositories
129+
- Grant only "Contents" read permission
130+
131+
**Then run the review with the token:**
132+
133+
```bash
134+
npx asyncreview review --url <URL> -q "question" --github-token $GITHUB_TOKEN
135+
```
136+
137+
Or set it as an environment variable for the session:
138+
139+
```bash
140+
export GITHUB_TOKEN=$(gh auth token)
141+
npx asyncreview review --url <URL> -q "question"
142+
```
143+
43144
## Quick start
44145

45146
```bash
@@ -105,6 +206,50 @@ The AI will:
105206
3. Analyze content in the Python sandbox
106207
4. Report findings with evidence
107208

209+
## Example: Review a Private Repository PR
210+
211+
**Complete workflow for private repositories:**
212+
213+
```bash
214+
# Step 1: Extract owner/repo from URL
215+
# URL: https://github.com/myorg/private-repo/pull/42
216+
# owner="myorg", repo="private-repo"
217+
218+
# Step 2: Check if repository is private
219+
220+
## Option A: With GitHub CLI
221+
gh repo view myorg/private-repo --json isPrivate -q '.isPrivate'
222+
# Output: true (it's private!)
223+
224+
## Option B: Without GitHub CLI (using curl)
225+
curl -s -o /dev/null -w "%{http_code}" https://api.github.com/repos/myorg/private-repo
226+
# Output: 404 (likely private or doesn't exist)
227+
228+
# Step 3: Ensure GITHUB_TOKEN is set
229+
echo $GITHUB_TOKEN
230+
231+
## If empty, Option A: Get from GitHub CLI
232+
export GITHUB_TOKEN=$(gh auth token)
233+
234+
## If empty, Option B: Create via web (https://github.com/settings/tokens)
235+
## Then:
236+
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
237+
238+
# Step 4: Run the review with the token
239+
npx asyncreview review \
240+
--url https://github.com/myorg/private-repo/pull/42 \
241+
-q "Does this PR introduce any security vulnerabilities?" \
242+
--github-token $GITHUB_TOKEN
243+
244+
# Alternative: Token is already in environment, no flag needed
245+
npx asyncreview review \
246+
--url https://github.com/myorg/private-repo/pull/42 \
247+
-q "Does this PR introduce any security vulnerabilities?"
248+
```
249+
250+
**If you get a 404 or authentication error:** The repository is likely private, and you need to provide `GITHUB_TOKEN`.
251+
252+
108253
## Output formats
109254

110255
| Format | Flag | Description |

0 commit comments

Comments
 (0)