agent-reviews 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.
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "agent-reviews",
3
+ "owner": {
4
+ "name": "Paul Bakaus"
5
+ },
6
+ "metadata": {
7
+ "description": "CLI and Claude Code skill for managing GitHub PR review comments"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "agent-reviews",
12
+ "source": "./",
13
+ "description": "Manage GitHub PR review comments — list, filter, reply, and watch for bot findings"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "agent-reviews",
3
+ "description": "Manage GitHub PR review comments from Claude Code. Automatically triage, fix, and respond to bot findings.",
4
+ "version": "0.1.0",
5
+ "author": {
6
+ "name": "Paul Bakaus",
7
+ "url": "https://github.com/pbakaus"
8
+ },
9
+ "homepage": "https://github.com/pbakaus/agent-reviews",
10
+ "repository": "https://github.com/pbakaus/agent-reviews",
11
+ "license": "MIT",
12
+ "keywords": ["github", "pull-request", "code-review", "claude-code", "ai-agent", "review-bot"]
13
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Paul Bakaus
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.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # agent-reviews
2
+
3
+ Manage GitHub PR review comments from the terminal and from AI coding agents.
4
+
5
+ PR review bots (Copilot, Cursor Bugbot, CodeRabbit, etc.) leave inline comments on your pull requests. agent-reviews gives you a CLI to list, filter, reply to, and watch those comments — plus a Claude Code skill that automates the entire triage-fix-reply loop.
6
+
7
+ ## Install
8
+
9
+ ### CLI (npm)
10
+
11
+ ```bash
12
+ npm install -g agent-reviews
13
+ ```
14
+
15
+ ### CLI (Homebrew)
16
+
17
+ ```bash
18
+ brew install agent-reviews
19
+ ```
20
+
21
+ ### Claude Code skill
22
+
23
+ Install as a skill for the full automated workflow — no npm install required:
24
+
25
+ ```bash
26
+ npx skills add pbakaus/agent-reviews@agent-reviews
27
+ ```
28
+
29
+ This registers the `/agent-reviews` slash command. When invoked, it uses `npx` to auto-download the CLI on first run.
30
+
31
+ > You can also use both: install the CLI globally for direct terminal use, and the skill for the Claude Code workflow.
32
+
33
+ ## Authentication
34
+
35
+ The primary authentication method is the **GitHub CLI** — if you're logged in with `gh auth login`, agent-reviews picks up the token automatically. No configuration needed.
36
+
37
+ For environments where `gh` isn't available (such as Claude Code Cloud, which routes git through an HTTPS proxy), agent-reviews falls back to:
38
+
39
+ 1. `GITHUB_TOKEN` environment variable
40
+ 2. `.env.local` in the repo root
41
+
42
+ The proxy environment is also why agent-reviews includes [undici](https://github.com/nodejs/undici) `ProxyAgent` support — when `HTTPS_PROXY` is set, GitHub API requests are routed through it automatically.
43
+
44
+ **Resolution order** (first match wins):
45
+
46
+ 1. `GITHUB_TOKEN` environment variable
47
+ 2. `.env.local` in the repo root
48
+ 3. `gh auth token` (GitHub CLI)
49
+
50
+ ## CLI Usage
51
+
52
+ ```bash
53
+ # List all review comments on the current branch's PR
54
+ agent-reviews
55
+
56
+ # Only unresolved comments
57
+ agent-reviews --unresolved
58
+
59
+ # Only unanswered bot comments
60
+ agent-reviews --unanswered --bots-only
61
+
62
+ # Full detail for a specific comment (diff hunk + replies)
63
+ agent-reviews --detail 12345678
64
+
65
+ # Reply to a comment
66
+ agent-reviews --reply 12345678 "Fixed in abc1234"
67
+
68
+ # JSON output for scripting / AI agents
69
+ agent-reviews --json
70
+
71
+ # Watch for new comments (polls every 30s, exits after 10 min idle)
72
+ agent-reviews --watch --bots-only
73
+
74
+ # Target a specific PR (otherwise auto-detects from branch)
75
+ agent-reviews --pr 42
76
+ ```
77
+
78
+ ### Options
79
+
80
+ | Flag | Short | Description |
81
+ |------|-------|-------------|
82
+ | `--unresolved` | `-u` | Only unresolved/pending comments |
83
+ | `--unanswered` | `-a` | Only comments without any replies |
84
+ | `--reply <id> "msg"` | `-r` | Reply to a comment |
85
+ | `--detail <id>` | `-d` | Full detail for a comment |
86
+ | `--pr <number>` | `-p` | Target a specific PR |
87
+ | `--json` | `-j` | JSON output |
88
+ | `--bots-only` | `-b` | Only bot comments |
89
+ | `--humans-only` | `-H` | Only human comments |
90
+ | `--watch` | `-w` | Poll for new comments |
91
+ | `--interval <sec>` | `-i` | Poll interval in seconds (default: 30) |
92
+ | `--timeout <sec>` | | Inactivity timeout in seconds (default: 600) |
93
+
94
+ ## Claude Code Skill
95
+
96
+ The `/agent-reviews` skill automates the full PR review bot workflow:
97
+
98
+ 1. Fetch all unanswered bot comments via `npx agent-reviews --bots-only --json`
99
+ 2. Evaluate each finding — true positive, false positive, or uncertain
100
+ 3. Fix real bugs and run lint/type-check
101
+ 4. Dismiss false positives with an explanation
102
+ 5. Reply to every comment with the outcome
103
+ 6. Watch for new comments as bots re-analyze the fixes
104
+ 7. Report a summary of all actions taken
105
+
106
+ ### Skill behavior
107
+
108
+ - **True positives** get fixed, verified, and replied with `✅ Fixed in {commit}`
109
+ - **False positives** get replied with `⚠️ Won't fix — {reason}`
110
+ - **Uncertain findings** prompt the user via `AskUserQuestion`
111
+ - All fixes are batched into a single commit before polling begins
112
+ - Watch mode runs for up to 10 minutes, processing any new findings
113
+
114
+ ## How It Works
115
+
116
+ ### Comment types
117
+
118
+ agent-reviews fetches three types of GitHub PR comments:
119
+
120
+ | Type | Label | Description |
121
+ |------|-------|-------------|
122
+ | Review comment | `CODE` | Inline comment attached to a specific line |
123
+ | Issue comment | `COMMENT` | General PR-level comment |
124
+ | Review | `REVIEW` | Review summary (approved, changes requested) |
125
+
126
+ ### Meta-comment filtering
127
+
128
+ Status updates from bots are automatically filtered out:
129
+
130
+ - Vercel deployment status (`[vc]:...`)
131
+ - Supabase branch status (`[supa]:...`)
132
+ - Cursor Bugbot summary ("Cursor Bugbot has reviewed your changes...")
133
+
134
+ Only actionable findings are shown.
135
+
136
+ ### Reply status
137
+
138
+ Each comment displays its reply status:
139
+
140
+ | Status | Meaning |
141
+ |--------|---------|
142
+ | `○ no reply` | No one has replied |
143
+ | `✓ replied` | A human has replied |
144
+ | `⚡ bot replied` | Only bots have replied |
145
+
146
+ ### Watch mode
147
+
148
+ Polls the GitHub API at a configurable interval and reports new comments as they appear. Outputs both formatted text and JSON for AI agent consumption. Exits automatically after a configurable inactivity timeout (default: 10 minutes).
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,506 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * agent-reviews — CLI for managing GitHub PR review comments
5
+ *
6
+ * List, filter, reply to, and watch PR review comments from the terminal.
7
+ * Designed for both human use and as a tool for AI coding agents.
8
+ *
9
+ * Usage:
10
+ * agent-reviews # List all review comments
11
+ * agent-reviews --unresolved # List unresolved comments only
12
+ * agent-reviews --unanswered # List comments without replies
13
+ * agent-reviews --reply <id> "msg" # Reply to a specific comment
14
+ * agent-reviews --detail <id> # Show full detail (no truncation)
15
+ * agent-reviews --json # Output as JSON for scripting
16
+ * agent-reviews --watch # Watch for new comments (poll mode)
17
+ *
18
+ * Options:
19
+ * --pr <number> Target specific PR (auto-detects from branch)
20
+ * --bots-only Only show bot comments
21
+ * --humans-only Only show human comments
22
+ */
23
+
24
+ const {
25
+ getProxyFetch,
26
+ getGitHubToken,
27
+ getRepoInfo,
28
+ getCurrentBranch,
29
+ } = require("../lib/github");
30
+
31
+ const {
32
+ findPRForBranch,
33
+ fetchPRComments,
34
+ processComments,
35
+ filterComments,
36
+ replyToComment,
37
+ } = require("../lib/comments");
38
+
39
+ const {
40
+ colors,
41
+ formatComment,
42
+ formatDetailedComment,
43
+ formatOutput,
44
+ } = require("../lib/format");
45
+
46
+ const proxyFetch = getProxyFetch();
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Argument parsing
50
+ // ---------------------------------------------------------------------------
51
+
52
+ function parseArgs() {
53
+ const args = process.argv.slice(2);
54
+ const result = {
55
+ command: "list",
56
+ prNumber: null,
57
+ filter: null,
58
+ replyTo: null,
59
+ replyMessage: null,
60
+ json: false,
61
+ botsOnly: false,
62
+ humansOnly: false,
63
+ detail: null,
64
+ help: false,
65
+ version: false,
66
+ watch: false,
67
+ watchInterval: 30,
68
+ watchTimeout: 600,
69
+ };
70
+
71
+ for (let i = 0; i < args.length; i++) {
72
+ switch (args[i]) {
73
+ case "--unresolved":
74
+ case "-u":
75
+ result.filter = "unresolved";
76
+ break;
77
+ case "--unanswered":
78
+ case "-a":
79
+ result.filter = "unanswered";
80
+ break;
81
+ case "--reply":
82
+ case "-r":
83
+ result.command = "reply";
84
+ result.replyTo = args[++i];
85
+ result.replyMessage = args[++i];
86
+ break;
87
+ case "--pr":
88
+ case "-p":
89
+ result.prNumber = Number.parseInt(args[++i], 10);
90
+ break;
91
+ case "--json":
92
+ case "-j":
93
+ result.json = true;
94
+ break;
95
+ case "--bots-only":
96
+ case "-b":
97
+ result.botsOnly = true;
98
+ break;
99
+ case "--humans-only":
100
+ case "-H":
101
+ result.humansOnly = true;
102
+ break;
103
+ case "--detail":
104
+ case "-d":
105
+ result.command = "detail";
106
+ result.detail = args[++i];
107
+ break;
108
+ case "--watch":
109
+ case "-w":
110
+ result.watch = true;
111
+ result.command = "watch";
112
+ break;
113
+ case "--interval":
114
+ case "-i":
115
+ result.watchInterval = Number.parseInt(args[++i], 10);
116
+ break;
117
+ case "--exit-after":
118
+ case "--timeout":
119
+ result.watchTimeout = Number.parseInt(args[++i], 10);
120
+ break;
121
+ case "--help":
122
+ case "-h":
123
+ result.help = true;
124
+ break;
125
+ case "--version":
126
+ case "-v":
127
+ result.version = true;
128
+ break;
129
+ default:
130
+ break;
131
+ }
132
+ }
133
+
134
+ return result;
135
+ }
136
+
137
+ function showHelp() {
138
+ console.log(`
139
+ ${colors.bright}agent-reviews${colors.reset} — Manage PR review comments from the CLI
140
+
141
+ Designed for both human use and as a tool for AI coding agents (Claude Code, etc.).
142
+
143
+ ${colors.bright}Usage:${colors.reset}
144
+ agent-reviews List all review comments
145
+ agent-reviews --unresolved List unresolved comments only
146
+ agent-reviews --unanswered List comments without replies
147
+ agent-reviews --reply <id> "msg" Reply to a specific comment
148
+ agent-reviews --detail <id> Show full detail for a comment
149
+ agent-reviews --watch Watch for new comments (poll mode)
150
+ agent-reviews --json Output as JSON for scripting
151
+
152
+ ${colors.bright}Options:${colors.reset}
153
+ -u, --unresolved Show only unresolved/pending comments
154
+ -a, --unanswered Show only comments without any replies
155
+ -r, --reply Reply to a comment (requires ID and message)
156
+ -d, --detail Show full detail for a specific comment
157
+ -p, --pr Target specific PR number (auto-detects from branch)
158
+ -j, --json Output as JSON instead of formatted text
159
+ -b, --bots-only Only show comments from bots
160
+ -H, --humans-only Only show comments from humans
161
+ -h, --help Show this help
162
+ -v, --version Show version
163
+
164
+ ${colors.bright}Watch Mode:${colors.reset}
165
+ -w, --watch Poll for new comments periodically
166
+ -i, --interval Poll interval in seconds (default: 30)
167
+ --timeout Exit after N seconds of inactivity (default: 600)
168
+
169
+ ${colors.bright}Examples:${colors.reset}
170
+ agent-reviews # Show all comments
171
+ agent-reviews -u # Show unresolved only
172
+ agent-reviews -a --bots-only # Unanswered bot comments
173
+ agent-reviews --reply 12345 "Fixed!" # Reply to comment #12345
174
+ agent-reviews --detail 12345 # Full detail for a comment
175
+ agent-reviews --detail 12345 --json # Detail as JSON
176
+ agent-reviews --json | jq '.[]' # Pipe to jq
177
+ agent-reviews --watch --bots-only # Watch for new bot comments
178
+ agent-reviews -w -i 15 --timeout 300 # Poll every 15s, exit after 5 min
179
+
180
+ ${colors.bright}Authentication:${colors.reset}
181
+ Set GITHUB_TOKEN env var, or use 'gh auth login' (gh CLI).
182
+
183
+ ${colors.dim}Comment IDs are shown in brackets, e.g., [12345678]${colors.reset}
184
+ `);
185
+ }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Watch mode
189
+ // ---------------------------------------------------------------------------
190
+
191
+ function formatTimestamp() {
192
+ return new Date().toISOString().replace("T", " ").slice(0, 19);
193
+ }
194
+
195
+ function sleep(seconds) {
196
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
197
+ }
198
+
199
+ async function watchForComments(context, options) {
200
+ const { owner, repo, prNumber, prUrl, token } = context;
201
+ const seenIds = new Set();
202
+ let lastActivityTime = Date.now();
203
+ let pollCount = 0;
204
+
205
+ function getWatchFilterDesc() {
206
+ if (options.botsOnly) return "bots-only";
207
+ if (options.humansOnly) return "humans-only";
208
+ return "all";
209
+ }
210
+ const filterDesc = getWatchFilterDesc();
211
+
212
+ console.log(
213
+ `\n${colors.bright}=== PR Comments Watch Mode ===${colors.reset}`
214
+ );
215
+ console.log(`${colors.dim}PR #${prNumber}: ${prUrl}${colors.reset}`);
216
+ console.log(
217
+ `${colors.dim}Polling every ${options.watchInterval}s, exit after ${options.watchTimeout}s of inactivity${colors.reset}`
218
+ );
219
+ console.log(
220
+ `${colors.dim}Filters: ${filterDesc}, ${options.filter || "all comments"}${colors.reset}`
221
+ );
222
+ console.log(
223
+ `${colors.dim}Started at ${formatTimestamp()}${colors.reset}\n`
224
+ );
225
+
226
+ // Initial fetch to populate seen IDs
227
+ const initialData = await fetchPRComments(
228
+ owner,
229
+ repo,
230
+ prNumber,
231
+ token,
232
+ proxyFetch
233
+ );
234
+ const initialProcessed = processComments(initialData);
235
+ const initialFiltered = filterComments(initialProcessed, options);
236
+
237
+ for (const comment of initialFiltered) {
238
+ seenIds.add(comment.id);
239
+ }
240
+
241
+ console.log(
242
+ `${colors.dim}[${formatTimestamp()}] Initial state: ${initialFiltered.length} existing comments tracked${colors.reset}`
243
+ );
244
+
245
+ if (initialFiltered.length > 0) {
246
+ console.log(`\n${colors.yellow}=== EXISTING COMMENTS ===${colors.reset}`);
247
+ for (const comment of initialFiltered) {
248
+ console.log(formatComment(comment));
249
+ console.log("");
250
+ }
251
+ }
252
+
253
+ // Watch loop
254
+ while (true) {
255
+ await sleep(options.watchInterval);
256
+ pollCount++;
257
+
258
+ const rawData = await fetchPRComments(
259
+ owner,
260
+ repo,
261
+ prNumber,
262
+ token,
263
+ proxyFetch
264
+ );
265
+ const processed = processComments(rawData);
266
+ const filtered = filterComments(processed, options);
267
+
268
+ const newComments = filtered.filter((c) => !seenIds.has(c.id));
269
+
270
+ if (newComments.length > 0) {
271
+ lastActivityTime = Date.now();
272
+
273
+ console.log(
274
+ `\n${colors.green}=== NEW COMMENTS DETECTED [${formatTimestamp()}] ===${colors.reset}`
275
+ );
276
+ console.log(
277
+ `${colors.bright}Found ${newComments.length} new comment${newComments.length === 1 ? "" : "s"}${colors.reset}\n`
278
+ );
279
+
280
+ for (const comment of newComments) {
281
+ seenIds.add(comment.id);
282
+ console.log(formatComment(comment));
283
+ console.log("");
284
+ }
285
+
286
+ // JSON output for AI agent parsing
287
+ console.log(`${colors.dim}--- JSON for processing ---${colors.reset}`);
288
+ console.log(JSON.stringify(newComments, null, 2));
289
+ console.log(`${colors.dim}--- end JSON ---${colors.reset}\n`);
290
+ } else {
291
+ const inactiveSeconds = Math.round(
292
+ (Date.now() - lastActivityTime) / 1000
293
+ );
294
+ console.log(
295
+ `${colors.dim}[${formatTimestamp()}] Poll #${pollCount}: No new comments (${inactiveSeconds}s/${options.watchTimeout}s idle)${colors.reset}`
296
+ );
297
+
298
+ if (inactiveSeconds >= options.watchTimeout) {
299
+ console.log(`\n${colors.green}=== WATCH COMPLETE ===${colors.reset}`);
300
+ console.log(
301
+ `${colors.dim}No new comments after ${options.watchTimeout}s of inactivity.${colors.reset}`
302
+ );
303
+ console.log(
304
+ `${colors.dim}Total comments tracked: ${seenIds.size}${colors.reset}`
305
+ );
306
+ console.log(
307
+ `${colors.dim}Exiting at ${formatTimestamp()}${colors.reset}`
308
+ );
309
+ return;
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // Main
317
+ // ---------------------------------------------------------------------------
318
+
319
+ async function main() {
320
+ const options = parseArgs();
321
+
322
+ if (options.version) {
323
+ const pkg = require("../package.json");
324
+ console.log(pkg.version);
325
+ process.exit(0);
326
+ }
327
+
328
+ if (options.help) {
329
+ showHelp();
330
+ process.exit(0);
331
+ }
332
+
333
+ // Get GitHub token
334
+ const token = getGitHubToken();
335
+ if (!token) {
336
+ console.error(`${colors.red}Error: GitHub token not found${colors.reset}`);
337
+ console.error(
338
+ "Set GITHUB_TOKEN env var, or authenticate with: gh auth login"
339
+ );
340
+ process.exit(1);
341
+ }
342
+
343
+ // Get repo info
344
+ const repoInfo = getRepoInfo();
345
+ if (!repoInfo) {
346
+ console.error(
347
+ `${colors.red}Error: Could not determine repository from git remote${colors.reset}`
348
+ );
349
+ process.exit(1);
350
+ }
351
+
352
+ // Find PR
353
+ let prNumber = options.prNumber;
354
+ let prUrl = null;
355
+
356
+ if (!prNumber) {
357
+ const branch = getCurrentBranch();
358
+ if (!branch) {
359
+ console.error(
360
+ `${colors.red}Error: Could not determine current branch${colors.reset}`
361
+ );
362
+ process.exit(1);
363
+ }
364
+
365
+ if (!options.json) {
366
+ console.log(
367
+ `${colors.dim}Finding PR for branch: ${branch}...${colors.reset}`
368
+ );
369
+ }
370
+
371
+ const pr = await findPRForBranch(
372
+ repoInfo.owner,
373
+ repoInfo.repo,
374
+ branch,
375
+ token,
376
+ proxyFetch
377
+ );
378
+ if (!pr) {
379
+ console.error(
380
+ `${colors.red}Error: No open PR found for branch '${branch}'${colors.reset}`
381
+ );
382
+ process.exit(1);
383
+ }
384
+
385
+ prNumber = pr.number;
386
+ prUrl = pr.html_url;
387
+ }
388
+
389
+ // Handle reply command
390
+ if (options.command === "reply") {
391
+ if (!(options.replyTo && options.replyMessage)) {
392
+ console.error(
393
+ `${colors.red}Error: --reply requires comment ID and message${colors.reset}`
394
+ );
395
+ console.error('Usage: agent-reviews --reply <id> "message"');
396
+ process.exit(1);
397
+ }
398
+
399
+ if (!options.json) {
400
+ console.log(
401
+ `${colors.dim}Replying to comment ${options.replyTo}...${colors.reset}`
402
+ );
403
+ }
404
+
405
+ const result = await replyToComment(
406
+ repoInfo.owner,
407
+ repoInfo.repo,
408
+ prNumber,
409
+ options.replyTo,
410
+ options.replyMessage,
411
+ token,
412
+ proxyFetch
413
+ );
414
+
415
+ if (options.json) {
416
+ console.log(JSON.stringify(result, null, 2));
417
+ } else {
418
+ console.log(
419
+ `${colors.green}✓ Reply posted successfully${colors.reset}`
420
+ );
421
+ console.log(` ${colors.dim}${result.html_url}${colors.reset}`);
422
+ }
423
+
424
+ return;
425
+ }
426
+
427
+ // Handle detail command
428
+ if (options.command === "detail") {
429
+ if (!options.detail) {
430
+ console.error(
431
+ `${colors.red}Error: --detail requires a comment ID${colors.reset}`
432
+ );
433
+ process.exit(1);
434
+ }
435
+
436
+ if (!options.json) {
437
+ console.log(
438
+ `${colors.dim}Fetching comments for PR #${prNumber}...${colors.reset}`
439
+ );
440
+ }
441
+
442
+ const rawData = await fetchPRComments(
443
+ repoInfo.owner,
444
+ repoInfo.repo,
445
+ prNumber,
446
+ token,
447
+ proxyFetch
448
+ );
449
+ const processed = processComments(rawData);
450
+ const targetId = Number(options.detail);
451
+ const comment = processed.find((c) => c.id === targetId);
452
+
453
+ if (!comment) {
454
+ console.error(
455
+ `${colors.red}Error: Comment ${options.detail} not found in PR #${prNumber}${colors.reset}`
456
+ );
457
+ process.exit(1);
458
+ }
459
+
460
+ if (options.json) {
461
+ console.log(JSON.stringify(comment, null, 2));
462
+ } else {
463
+ console.log(formatDetailedComment(comment));
464
+ }
465
+
466
+ return;
467
+ }
468
+
469
+ // Handle watch command
470
+ if (options.command === "watch") {
471
+ await watchForComments(
472
+ { owner: repoInfo.owner, repo: repoInfo.repo, prNumber, prUrl, token },
473
+ options
474
+ );
475
+ return;
476
+ }
477
+
478
+ // Default: fetch and display comments
479
+ if (!options.json) {
480
+ console.log(
481
+ `${colors.dim}Fetching comments for PR #${prNumber}...${colors.reset}`
482
+ );
483
+ }
484
+
485
+ const rawData = await fetchPRComments(
486
+ repoInfo.owner,
487
+ repoInfo.repo,
488
+ prNumber,
489
+ token,
490
+ proxyFetch
491
+ );
492
+
493
+ const processed = processComments(rawData);
494
+ const filtered = filterComments(processed, options);
495
+
496
+ console.log(formatOutput(filtered, options));
497
+
498
+ if (!options.json && prUrl) {
499
+ console.log(`\n${colors.dim}PR: ${prUrl}${colors.reset}`);
500
+ }
501
+ }
502
+
503
+ main().catch((error) => {
504
+ console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
505
+ process.exit(1);
506
+ });