prq-cli 0.5.0 → 0.7.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.
- package/README.md +129 -56
- package/dist/bin/prq.js +198 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
PR Queue — see what code reviews need your attention.
|
|
6
6
|
|
|
7
|
-
A CLI tool that
|
|
7
|
+
A CLI tool that shows you a categorized view of PRs that need action — then lets you act on them with whatever tools you already use. PRQ is the queue. You bring the workflow.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -17,83 +17,169 @@ Requires [GitHub CLI](https://cli.github.com/) (`gh`) to be authenticated.
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
# See your review queue
|
|
20
|
+
# See your review queue (interactive by default)
|
|
21
21
|
prq
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
prq -
|
|
23
|
+
# Non-interactive / plain text
|
|
24
|
+
prq --no-interactive
|
|
25
|
+
|
|
26
|
+
# Act on a PR
|
|
27
|
+
prq review 482
|
|
28
|
+
prq open 482
|
|
29
|
+
prq nudge 482
|
|
25
30
|
```
|
|
26
31
|
|
|
27
32
|
## Commands
|
|
28
33
|
|
|
29
34
|
### `prq status` (default)
|
|
30
35
|
|
|
31
|
-
Shows PRs needing your attention
|
|
36
|
+
Shows PRs needing your attention in four categories:
|
|
32
37
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
38
|
+
- **◆ Needs Re-review** — new commits pushed after your review
|
|
39
|
+
- **● Requested** — you're a requested reviewer
|
|
40
|
+
- **○ Stale** — no activity for N days
|
|
41
|
+
- **◇ Your PRs Waiting** — waiting on someone else
|
|
37
42
|
|
|
38
43
|
```bash
|
|
39
|
-
prq #
|
|
44
|
+
prq # interactive mode (default)
|
|
40
45
|
prq status --repos org/repo1 org/repo2 # specific repos
|
|
41
|
-
prq status --stale-days 7 # custom
|
|
42
|
-
prq status --json # machine-readable
|
|
43
|
-
prq
|
|
46
|
+
prq status --stale-days 7 # custom threshold
|
|
47
|
+
prq status --json # machine-readable
|
|
48
|
+
prq --no-interactive # plain text output
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
### Interactive Mode
|
|
51
|
+
### Interactive Mode (default)
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
Interactive mode is the default when running in a terminal. Navigate your queue with keyboard shortcuts:
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
| Key | Action |
|
|
56
|
+
|-----|--------|
|
|
57
|
+
| ↑↓ | Navigate between PRs |
|
|
58
|
+
| r | Review — open files changed |
|
|
59
|
+
| o | Open — open PR in browser |
|
|
60
|
+
| n | Nudge — post a comment |
|
|
61
|
+
| c | Copy URL to clipboard |
|
|
62
|
+
| a | Actions — open menu with all actions |
|
|
63
|
+
| q | Quit |
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
Press **a** to open the actions menu, which lists all actions (built-in and custom from your config). Press **1-9** to run an action, or **q** to dismiss.
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
### `prq open/review/nudge <identifier>`
|
|
68
|
+
|
|
69
|
+
Act on PRs by number, `org/repo#number`, or full URL:
|
|
60
70
|
|
|
61
71
|
```bash
|
|
62
|
-
prq open 482 #
|
|
63
|
-
prq
|
|
64
|
-
prq
|
|
72
|
+
prq open 482 # open in browser
|
|
73
|
+
prq review 482 # open files changed
|
|
74
|
+
prq nudge 482 # post a comment
|
|
75
|
+
prq nudge 482 --yes --message "Update?" # skip confirmation
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
### `prq
|
|
78
|
+
### `prq run <action> <identifier>`
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
Run any custom action you've defined:
|
|
70
81
|
|
|
71
82
|
```bash
|
|
72
|
-
prq
|
|
73
|
-
prq review
|
|
83
|
+
prq run checkout 482
|
|
84
|
+
prq run ai-review 482
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
### `prq
|
|
87
|
+
### `prq skill`
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
Install the `/prq` skill for Claude Code:
|
|
79
90
|
|
|
80
91
|
```bash
|
|
81
|
-
prq
|
|
82
|
-
prq
|
|
83
|
-
prq nudge 482 --message "Any updates?" # custom message
|
|
92
|
+
prq skill # install in current project
|
|
93
|
+
prq skill --global # install globally
|
|
84
94
|
```
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
## Pluggable Actions
|
|
97
|
+
|
|
98
|
+
PRQ doesn't force a workflow. Every action is a configurable shell command template. Override the defaults or add your own in `.prqrc.json`:
|
|
99
|
+
|
|
100
|
+
### Use Claude Code for reviews
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"actions": {
|
|
105
|
+
"review": "claude -p '/review {url}'"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Now `prq review 482` dispatches to Claude Code.
|
|
111
|
+
|
|
112
|
+
### Use Codex for reviews
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"actions": {
|
|
117
|
+
"review": "codex exec --full-auto 'review the PR at {url}'"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Use gh CLI to checkout
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"actions": {
|
|
127
|
+
"checkout": "gh pr checkout {number} --repo {owner}/{repo}"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then `prq run checkout 482`.
|
|
87
133
|
|
|
88
|
-
|
|
134
|
+
### Just open in browser (default)
|
|
135
|
+
|
|
136
|
+
With no config, `prq review` opens the files changed tab and `prq open` opens the PR page. Zero setup needed.
|
|
137
|
+
|
|
138
|
+
### Template variables
|
|
139
|
+
|
|
140
|
+
| Variable | Example |
|
|
141
|
+
|----------|---------|
|
|
142
|
+
| `{url}` | `https://github.com/org/repo/pull/482` |
|
|
143
|
+
| `{number}` | `482` |
|
|
144
|
+
| `{owner}` | `org` |
|
|
145
|
+
| `{repo}` | `repo` |
|
|
146
|
+
| `{title}` | `fix: handle edge case` |
|
|
147
|
+
| `{author}` | `alice` |
|
|
148
|
+
| `{days}` | `5` |
|
|
149
|
+
|
|
150
|
+
## Agent & Automation
|
|
151
|
+
|
|
152
|
+
PRQ is fully scriptable with `--json` output and `--yes` flags:
|
|
89
153
|
|
|
90
154
|
```bash
|
|
91
|
-
|
|
155
|
+
# Agent reads the queue
|
|
156
|
+
prq status --json
|
|
157
|
+
|
|
158
|
+
# Agent nudges all stale PRs
|
|
159
|
+
prq status --json | jq -r '.prs[] | select(.category == "stale") | .number' \
|
|
160
|
+
| xargs -I{} prq nudge {} --yes
|
|
161
|
+
|
|
162
|
+
# Claude Code cron
|
|
163
|
+
prq status --json | claude -p "Review needs-re-review PRs older than 7 days"
|
|
92
164
|
```
|
|
93
165
|
|
|
94
|
-
###
|
|
166
|
+
### Claude Code Skill
|
|
167
|
+
|
|
168
|
+
Install the `/prq` skill to use PRQ inside Claude Code sessions:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
prq skill --global
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Then in Claude Code:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
/prq → show queue, ask what to do
|
|
178
|
+
"review 2439" → dispatches to your configured review action
|
|
179
|
+
"nudge all stale PRs" → batch nudge
|
|
180
|
+
```
|
|
95
181
|
|
|
96
|
-
|
|
182
|
+
The skill reads your `.prqrc.json` actions — if you have `/review` configured, it uses that. If not, it falls back to opening the browser.
|
|
97
183
|
|
|
98
184
|
## Configuration
|
|
99
185
|
|
|
@@ -103,7 +189,7 @@ Config is loaded in this order (later overrides earlier):
|
|
|
103
189
|
2. `.prqrc.json` — per-project config
|
|
104
190
|
3. CLI flags
|
|
105
191
|
|
|
106
|
-
|
|
192
|
+
Full example:
|
|
107
193
|
|
|
108
194
|
```json
|
|
109
195
|
{
|
|
@@ -111,25 +197,12 @@ Example `.prqrc.json`:
|
|
|
111
197
|
"staleDays": 5,
|
|
112
198
|
"actions": {
|
|
113
199
|
"review": "claude -p '/review {url}'",
|
|
114
|
-
"checkout": "gh pr checkout {number} --repo {owner}/{repo}"
|
|
200
|
+
"checkout": "gh pr checkout {number} --repo {owner}/{repo}",
|
|
201
|
+
"approve": "gh pr review {number} --repo {owner}/{repo} --approve"
|
|
115
202
|
}
|
|
116
203
|
}
|
|
117
204
|
```
|
|
118
205
|
|
|
119
|
-
### Custom Actions
|
|
120
|
-
|
|
121
|
-
Actions are shell command templates with variables:
|
|
122
|
-
|
|
123
|
-
- `{url}` — full PR URL
|
|
124
|
-
- `{number}` — PR number
|
|
125
|
-
- `{owner}` — repo owner
|
|
126
|
-
- `{repo}` — repo name
|
|
127
|
-
- `{title}` — PR title
|
|
128
|
-
- `{author}` — PR author
|
|
129
|
-
- `{days}` — days since last activity
|
|
130
|
-
|
|
131
|
-
Default actions (`open`, `review`, `nudge`) can be overridden. Custom actions are available via `prq run <action>` and in interactive mode.
|
|
132
|
-
|
|
133
206
|
## License
|
|
134
207
|
|
|
135
208
|
MIT
|
package/dist/bin/prq.js
CHANGED
|
@@ -6478,6 +6478,122 @@ Available actions: ${available}`);
|
|
|
6478
6478
|
await executeCommand(command);
|
|
6479
6479
|
}
|
|
6480
6480
|
|
|
6481
|
+
// src/commands/skill.ts
|
|
6482
|
+
var SKILL_CONTENT = `---
|
|
6483
|
+
name: prq
|
|
6484
|
+
description: PR review queue manager. Use when the user wants to check their PR review queue, review PRs, nudge stale PRs, or manage code review workflow. Triggers on mentions of "review queue", "PRs waiting", "stale PRs", "what needs review", or "prq".
|
|
6485
|
+
---
|
|
6486
|
+
|
|
6487
|
+
# PRQ — PR Review Queue
|
|
6488
|
+
|
|
6489
|
+
You have access to the \`prq\` CLI tool installed on this machine. Use it to help the user manage their code review queue.
|
|
6490
|
+
|
|
6491
|
+
## Step 1: Get the queue
|
|
6492
|
+
|
|
6493
|
+
Run this command to get the user's current review queue:
|
|
6494
|
+
|
|
6495
|
+
\`\`\`bash
|
|
6496
|
+
prq status --json
|
|
6497
|
+
\`\`\`
|
|
6498
|
+
|
|
6499
|
+
This returns a JSON object with categorized PRs:
|
|
6500
|
+
- **needs-re-review** — PRs where the user left a review but new commits were pushed after
|
|
6501
|
+
- **requested** — PRs where the user is a requested reviewer
|
|
6502
|
+
- **stale** — PRs with no activity for N days
|
|
6503
|
+
- **waiting-on-others** — PRs the user authored that are waiting on someone else
|
|
6504
|
+
|
|
6505
|
+
## Step 2: Present the queue
|
|
6506
|
+
|
|
6507
|
+
Show the results in a clear, scannable format grouped by category. For each PR, show:
|
|
6508
|
+
- The category symbol (◆ needs re-review, ● requested, ○ stale, ◇ waiting)
|
|
6509
|
+
- The repo and PR number
|
|
6510
|
+
- The title
|
|
6511
|
+
- The detail (e.g., "new commits since your review 2d ago")
|
|
6512
|
+
|
|
6513
|
+
Then ask what the user wants to do.
|
|
6514
|
+
|
|
6515
|
+
## Step 3: Act on PRs
|
|
6516
|
+
|
|
6517
|
+
When the user asks to act on a PR, check the \`.prqrc.json\` file in the current directory (or \`~/.config/prq/config.json\`) for custom actions:
|
|
6518
|
+
|
|
6519
|
+
\`\`\`json
|
|
6520
|
+
{
|
|
6521
|
+
"actions": {
|
|
6522
|
+
"review": "/review {url}",
|
|
6523
|
+
"nudge": "shell:prq nudge {number} --yes"
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
\`\`\`
|
|
6527
|
+
|
|
6528
|
+
### Action resolution
|
|
6529
|
+
|
|
6530
|
+
For each action template:
|
|
6531
|
+
- **Starts with \`/\`** — it's a Claude Code skill. Invoke it by running the skill with the interpolated value. For example, \`/review https://github.com/org/repo/pull/123\`
|
|
6532
|
+
- **Starts with \`shell:\`** — it's a shell command. Run it with the Bash tool. For example, \`prq nudge 123 --yes\`
|
|
6533
|
+
- **Otherwise** — treat it as a prompt. Send it as a message.
|
|
6534
|
+
|
|
6535
|
+
### Template variables
|
|
6536
|
+
|
|
6537
|
+
Replace these in the action template:
|
|
6538
|
+
- \`{url}\` — full PR URL
|
|
6539
|
+
- \`{number}\` — PR number
|
|
6540
|
+
- \`{owner}\` — repo owner
|
|
6541
|
+
- \`{repo}\` — repo name
|
|
6542
|
+
- \`{title}\` — PR title
|
|
6543
|
+
- \`{author}\` — PR author
|
|
6544
|
+
|
|
6545
|
+
### Default actions (if no config found)
|
|
6546
|
+
|
|
6547
|
+
If no actions are configured, use these defaults:
|
|
6548
|
+
- **review** — invoke \`/review {url}\` if the /review skill exists, otherwise run \`prq review {number}\` to open files changed in browser
|
|
6549
|
+
- **nudge** — run \`prq nudge {number} --yes\`
|
|
6550
|
+
- **open** — run \`prq open {number}\`
|
|
6551
|
+
|
|
6552
|
+
## Step 4: Batch operations
|
|
6553
|
+
|
|
6554
|
+
If the user says things like "review all needs-re-review PRs" or "nudge all stale PRs":
|
|
6555
|
+
|
|
6556
|
+
1. Filter the queue JSON by the requested category
|
|
6557
|
+
2. Confirm the list with the user: "I'll review these 3 PRs: #2439, #2380, #2352. Proceed?"
|
|
6558
|
+
3. Execute the action on each PR sequentially
|
|
6559
|
+
|
|
6560
|
+
## Examples
|
|
6561
|
+
|
|
6562
|
+
**User:** "check my review queue"
|
|
6563
|
+
→ Run \`prq status --json\`, present results, ask what to do
|
|
6564
|
+
|
|
6565
|
+
**User:** "review 2439"
|
|
6566
|
+
→ Look up action for "review", interpolate with PR data, execute
|
|
6567
|
+
|
|
6568
|
+
**User:** "nudge all stale PRs"
|
|
6569
|
+
→ Filter stale PRs from queue, confirm, run nudge on each
|
|
6570
|
+
|
|
6571
|
+
**User:** "what PRs are waiting on me?"
|
|
6572
|
+
→ Run \`prq status --json\`, show only needs-re-review and requested categories
|
|
6573
|
+
|
|
6574
|
+
**User:** "/prq" with no context
|
|
6575
|
+
→ Run \`prq status --json\`, present full queue, ask what to do
|
|
6576
|
+
`;
|
|
6577
|
+
function skillCommand(global) {
|
|
6578
|
+
if (global) {
|
|
6579
|
+
const fs2 = __require("node:fs");
|
|
6580
|
+
const path2 = __require("node:path");
|
|
6581
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6582
|
+
const skillDir = path2.join(home, ".claude", "skills", "prq");
|
|
6583
|
+
const skillPath = path2.join(skillDir, "SKILL.md");
|
|
6584
|
+
fs2.mkdirSync(skillDir, { recursive: true });
|
|
6585
|
+
fs2.writeFileSync(skillPath, SKILL_CONTENT);
|
|
6586
|
+
console.log(`Installed to ${skillPath}`);
|
|
6587
|
+
} else {
|
|
6588
|
+
const fs2 = __require("node:fs");
|
|
6589
|
+
const skillDir = ".claude/skills/prq";
|
|
6590
|
+
const skillPath = `${skillDir}/SKILL.md`;
|
|
6591
|
+
fs2.mkdirSync(skillDir, { recursive: true });
|
|
6592
|
+
fs2.writeFileSync(skillPath, SKILL_CONTENT);
|
|
6593
|
+
console.log(`Installed to ${skillPath}`);
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6481
6597
|
// src/categorize.ts
|
|
6482
6598
|
function timeAgo(dateStr) {
|
|
6483
6599
|
const now = Date.now();
|
|
@@ -6616,7 +6732,8 @@ function toResolvedPR(pr) {
|
|
|
6616
6732
|
updatedAt: pr.updatedAt
|
|
6617
6733
|
};
|
|
6618
6734
|
}
|
|
6619
|
-
function render(
|
|
6735
|
+
function render(state) {
|
|
6736
|
+
const { result, selectedIndex, message, actionMenu } = state;
|
|
6620
6737
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
6621
6738
|
const lines = [];
|
|
6622
6739
|
lines.push(source_default.bold(` PRQ Status for ${result.user}`));
|
|
@@ -6659,8 +6776,20 @@ function render(result, selectedIndex, message) {
|
|
|
6659
6776
|
}
|
|
6660
6777
|
lines.push("");
|
|
6661
6778
|
lines.push(source_default.dim(` ${"─".repeat(50)}`));
|
|
6662
|
-
|
|
6663
|
-
|
|
6779
|
+
if (actionMenu) {
|
|
6780
|
+
const pr = result.prs[selectedIndex];
|
|
6781
|
+
lines.push("");
|
|
6782
|
+
lines.push(` ${source_default.bold("Actions")} for ${source_default.white(`#${pr.number}`)}`);
|
|
6783
|
+
lines.push("");
|
|
6784
|
+
for (let i = 0;i < actionMenu.length; i++) {
|
|
6785
|
+
lines.push(` ${source_default.white(String(i + 1))}. ${actionMenu[i].name}`);
|
|
6786
|
+
}
|
|
6787
|
+
lines.push("");
|
|
6788
|
+
lines.push(` ${source_default.dim("1-9")} run action ${source_default.white("q")} back`);
|
|
6789
|
+
} else {
|
|
6790
|
+
lines.push("");
|
|
6791
|
+
lines.push(` ${source_default.dim("↑↓")} navigate ${source_default.white("r")} review ${source_default.white("o")} open ${source_default.white("n")} nudge ${source_default.white("c")} copy url ${source_default.white("a")} actions ${source_default.white("q")} quit`);
|
|
6792
|
+
}
|
|
6664
6793
|
if (message) {
|
|
6665
6794
|
lines.push("");
|
|
6666
6795
|
lines.push(` ${message}`);
|
|
@@ -6668,6 +6797,17 @@ function render(result, selectedIndex, message) {
|
|
|
6668
6797
|
process.stdout.write(lines.join(`
|
|
6669
6798
|
`));
|
|
6670
6799
|
}
|
|
6800
|
+
async function runAction(actionName, template, pr, state) {
|
|
6801
|
+
const cmd = interpolate(template, buildContext(toResolvedPR(pr)));
|
|
6802
|
+
state.message = source_default.dim(`running ${actionName} on ${pr.repo}#${pr.number}...`);
|
|
6803
|
+
render(state);
|
|
6804
|
+
try {
|
|
6805
|
+
await executeCommand(cmd);
|
|
6806
|
+
return source_default.green(`${actionName}: ${pr.repo}#${pr.number}`);
|
|
6807
|
+
} catch {
|
|
6808
|
+
return source_default.red(`${actionName} failed`);
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6671
6811
|
async function interactiveMode(result, config) {
|
|
6672
6812
|
if (result.prs.length === 0) {
|
|
6673
6813
|
console.log(source_default.green(`
|
|
@@ -6675,9 +6815,14 @@ async function interactiveMode(result, config) {
|
|
|
6675
6815
|
`));
|
|
6676
6816
|
return;
|
|
6677
6817
|
}
|
|
6678
|
-
let selectedIndex = 0;
|
|
6679
|
-
let message = "";
|
|
6680
6818
|
const total = result.prs.length;
|
|
6819
|
+
const allActions = listActions(config);
|
|
6820
|
+
const state = {
|
|
6821
|
+
result,
|
|
6822
|
+
selectedIndex: 0,
|
|
6823
|
+
message: "",
|
|
6824
|
+
actionMenu: null
|
|
6825
|
+
};
|
|
6681
6826
|
if (!process.stdin.isTTY) {
|
|
6682
6827
|
process.stdout.write(`Interactive mode requires a terminal. Use prq status instead.
|
|
6683
6828
|
`);
|
|
@@ -6687,7 +6832,7 @@ async function interactiveMode(result, config) {
|
|
|
6687
6832
|
process.stdin.resume();
|
|
6688
6833
|
process.stdin.setEncoding("utf8");
|
|
6689
6834
|
process.stdout.write("\x1B[?25l");
|
|
6690
|
-
render(
|
|
6835
|
+
render(state);
|
|
6691
6836
|
return new Promise((resolve) => {
|
|
6692
6837
|
const cleanup = () => {
|
|
6693
6838
|
process.stdin.setRawMode(false);
|
|
@@ -6696,7 +6841,26 @@ async function interactiveMode(result, config) {
|
|
|
6696
6841
|
process.stdout.write("\x1B[?25h\x1B[2J\x1B[H");
|
|
6697
6842
|
};
|
|
6698
6843
|
process.stdin.on("data", async (key) => {
|
|
6699
|
-
const pr = result.prs[selectedIndex];
|
|
6844
|
+
const pr = result.prs[state.selectedIndex];
|
|
6845
|
+
if (state.actionMenu) {
|
|
6846
|
+
if (key === "q" || key === "\x1B" || key === "a") {
|
|
6847
|
+
state.actionMenu = null;
|
|
6848
|
+
state.message = "";
|
|
6849
|
+
} else if (key === "\x03") {
|
|
6850
|
+
cleanup();
|
|
6851
|
+
resolve();
|
|
6852
|
+
return;
|
|
6853
|
+
} else {
|
|
6854
|
+
const idx = parseInt(key, 10);
|
|
6855
|
+
if (idx >= 1 && idx <= state.actionMenu.length) {
|
|
6856
|
+
const action = state.actionMenu[idx - 1];
|
|
6857
|
+
state.message = await runAction(action.name, action.template, pr, state);
|
|
6858
|
+
state.actionMenu = null;
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6861
|
+
render(state);
|
|
6862
|
+
return;
|
|
6863
|
+
}
|
|
6700
6864
|
switch (key) {
|
|
6701
6865
|
case "q":
|
|
6702
6866
|
case "\x03":
|
|
@@ -6704,55 +6868,31 @@ async function interactiveMode(result, config) {
|
|
|
6704
6868
|
resolve();
|
|
6705
6869
|
return;
|
|
6706
6870
|
case "\x1B[A":
|
|
6707
|
-
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
6708
|
-
message = "";
|
|
6871
|
+
state.selectedIndex = Math.max(0, state.selectedIndex - 1);
|
|
6872
|
+
state.message = "";
|
|
6709
6873
|
break;
|
|
6710
6874
|
case "\x1B[B":
|
|
6711
|
-
selectedIndex = Math.min(total - 1, selectedIndex + 1);
|
|
6712
|
-
message = "";
|
|
6875
|
+
state.selectedIndex = Math.min(total - 1, state.selectedIndex + 1);
|
|
6876
|
+
state.message = "";
|
|
6713
6877
|
break;
|
|
6714
6878
|
case "o": {
|
|
6715
|
-
const template =
|
|
6879
|
+
const template = allActions.open;
|
|
6716
6880
|
if (template) {
|
|
6717
|
-
|
|
6718
|
-
message = source_default.dim(`opening ${pr.repo}#${pr.number}...`);
|
|
6719
|
-
render(result, selectedIndex, message);
|
|
6720
|
-
try {
|
|
6721
|
-
await executeCommand(cmd);
|
|
6722
|
-
message = source_default.green(`opened ${pr.repo}#${pr.number}`);
|
|
6723
|
-
} catch {
|
|
6724
|
-
message = source_default.red("failed to open");
|
|
6725
|
-
}
|
|
6881
|
+
state.message = await runAction("open", template, pr, state);
|
|
6726
6882
|
}
|
|
6727
6883
|
break;
|
|
6728
6884
|
}
|
|
6729
6885
|
case "r": {
|
|
6730
|
-
const template =
|
|
6886
|
+
const template = allActions.review;
|
|
6731
6887
|
if (template) {
|
|
6732
|
-
|
|
6733
|
-
message = source_default.dim(`opening review for ${pr.repo}#${pr.number}...`);
|
|
6734
|
-
render(result, selectedIndex, message);
|
|
6735
|
-
try {
|
|
6736
|
-
await executeCommand(cmd);
|
|
6737
|
-
message = source_default.green(`opened review for ${pr.repo}#${pr.number}`);
|
|
6738
|
-
} catch {
|
|
6739
|
-
message = source_default.red("failed to open review");
|
|
6740
|
-
}
|
|
6888
|
+
state.message = await runAction("review", template, pr, state);
|
|
6741
6889
|
}
|
|
6742
6890
|
break;
|
|
6743
6891
|
}
|
|
6744
6892
|
case "n": {
|
|
6745
|
-
const template =
|
|
6893
|
+
const template = allActions.nudge;
|
|
6746
6894
|
if (template) {
|
|
6747
|
-
|
|
6748
|
-
message = source_default.dim(`nudging ${pr.repo}#${pr.number}...`);
|
|
6749
|
-
render(result, selectedIndex, message);
|
|
6750
|
-
try {
|
|
6751
|
-
await executeCommand(cmd);
|
|
6752
|
-
message = source_default.green(`nudged ${pr.repo}#${pr.number}`);
|
|
6753
|
-
} catch {
|
|
6754
|
-
message = source_default.red("failed to nudge");
|
|
6755
|
-
}
|
|
6895
|
+
state.message = await runAction("nudge", template, pr, state);
|
|
6756
6896
|
}
|
|
6757
6897
|
break;
|
|
6758
6898
|
}
|
|
@@ -6761,16 +6901,25 @@ async function interactiveMode(result, config) {
|
|
|
6761
6901
|
try {
|
|
6762
6902
|
const proc = process.platform === "darwin" ? "pbcopy" : process.platform === "linux" ? "xclip -selection clipboard" : "clip";
|
|
6763
6903
|
await executeCommand(`echo "${url}" | ${proc}`);
|
|
6764
|
-
message = source_default.green("url copied");
|
|
6904
|
+
state.message = source_default.green("url copied");
|
|
6765
6905
|
} catch {
|
|
6766
|
-
message = source_default.dim(url);
|
|
6906
|
+
state.message = source_default.dim(url);
|
|
6767
6907
|
}
|
|
6768
6908
|
break;
|
|
6769
6909
|
}
|
|
6910
|
+
case "a": {
|
|
6911
|
+
const entries = Object.entries(allActions);
|
|
6912
|
+
state.actionMenu = entries.map(([name, template]) => ({
|
|
6913
|
+
name,
|
|
6914
|
+
template
|
|
6915
|
+
}));
|
|
6916
|
+
state.message = "";
|
|
6917
|
+
break;
|
|
6918
|
+
}
|
|
6770
6919
|
default:
|
|
6771
6920
|
break;
|
|
6772
6921
|
}
|
|
6773
|
-
render(
|
|
6922
|
+
render(state);
|
|
6774
6923
|
});
|
|
6775
6924
|
});
|
|
6776
6925
|
}
|
|
@@ -10893,12 +11042,12 @@ function getVersion() {
|
|
|
10893
11042
|
function createCLI() {
|
|
10894
11043
|
const program2 = new Command;
|
|
10895
11044
|
program2.name("prq").description("PR Queue — see what code reviews need your attention").version(getVersion());
|
|
10896
|
-
program2.command("status", { isDefault: true }).description("Show PRs needing your attention").option("-r, --repos <repos...>", "Filter to specific repos (owner/name)").option("-s, --stale-days <days>", "Days of inactivity to consider stale", "3").option("--json", "Output as JSON").option("-
|
|
11045
|
+
program2.command("status", { isDefault: true }).description("Show PRs needing your attention").option("-r, --repos <repos...>", "Filter to specific repos (owner/name)").option("-s, --stale-days <days>", "Days of inactivity to consider stale", "3").option("--json", "Output as JSON").option("--no-interactive", "Disable interactive mode").action(async (opts) => {
|
|
10897
11046
|
const config = loadConfig({
|
|
10898
11047
|
repos: opts.repos,
|
|
10899
11048
|
staleDays: opts.staleDays ? parseInt(opts.staleDays, 10) : undefined
|
|
10900
11049
|
});
|
|
10901
|
-
await statusCommand(config, opts.json ?? false, opts.interactive ??
|
|
11050
|
+
await statusCommand(config, opts.json ?? false, opts.interactive ?? true);
|
|
10902
11051
|
});
|
|
10903
11052
|
program2.command("open <identifier>").description("Open a PR in the browser").action(async (identifier) => {
|
|
10904
11053
|
const config = loadConfig({});
|
|
@@ -10919,6 +11068,9 @@ function createCLI() {
|
|
|
10919
11068
|
const config = loadConfig({});
|
|
10920
11069
|
await runCommand(action, identifier, config);
|
|
10921
11070
|
});
|
|
11071
|
+
program2.command("skill").description("Install the /prq skill for Claude Code").option("-g, --global", "Install globally (~/.claude/skills/prq/)").action((opts) => {
|
|
11072
|
+
skillCommand(opts.global ?? false);
|
|
11073
|
+
});
|
|
10922
11074
|
program2.command("init").description("Create config file interactively").action(async () => {
|
|
10923
11075
|
await initCommand();
|
|
10924
11076
|
});
|