prq-cli 0.9.0 → 0.10.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 +9 -6
- package/dist/bin/prq.js +97 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
PR Queue — see what code reviews need your attention.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Four categories. What needs re-review, what's requested, what's stale, what's waiting on others. `prq` is the queue. You bring the workflow.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -40,6 +40,8 @@ Shows PRs needing your attention in four categories:
|
|
|
40
40
|
- **○ Stale** — no activity for N days
|
|
41
41
|
- **◇ Your PRs Waiting** — waiting on someone else
|
|
42
42
|
|
|
43
|
+
PRs you mark as started appear in a separate **▸ In Progress** group at the top.
|
|
44
|
+
|
|
43
45
|
```bash
|
|
44
46
|
prq # interactive mode (default)
|
|
45
47
|
prq status --repos org/repo1 org/repo2 # specific repos
|
|
@@ -58,6 +60,7 @@ Interactive mode is the default when running in a terminal. Navigate your queue
|
|
|
58
60
|
| r | Review — open files changed |
|
|
59
61
|
| o | Open — open PR in browser |
|
|
60
62
|
| n | Nudge — post a comment |
|
|
63
|
+
| s | Start/Stop — mark as in progress |
|
|
61
64
|
| c | Copy URL to clipboard |
|
|
62
65
|
| a | Actions — open menu with all actions |
|
|
63
66
|
| q | Quit |
|
|
@@ -93,9 +96,9 @@ prq skill # install in current project
|
|
|
93
96
|
prq skill --global # install globally
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
##
|
|
99
|
+
## Custom Actions
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
`prq` doesn't force a workflow. Every action is a configurable shell command template — inline commands or scripts. Override the defaults or add your own in `.prqrc.json`.
|
|
99
102
|
|
|
100
103
|
Actions run with full terminal control. When you trigger an action, prq suspends its TUI, the command takes over the screen (interactive tools like Claude Code work as normal), and prq resumes when the command exits.
|
|
101
104
|
|
|
@@ -163,9 +166,9 @@ With no config, `prq review` opens the files changed tab and `prq open` opens th
|
|
|
163
166
|
| `{days}` | `5` |
|
|
164
167
|
| `{category}` | `needs-re-review` |
|
|
165
168
|
|
|
166
|
-
##
|
|
169
|
+
## Scripting
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
`prq` is fully scriptable with `--json` output and `--yes` flags:
|
|
169
172
|
|
|
170
173
|
```bash
|
|
171
174
|
# Agent reads the queue
|
|
@@ -181,7 +184,7 @@ prq status --json | claude -p "Review needs-re-review PRs older than 7 days"
|
|
|
181
184
|
|
|
182
185
|
### Claude Code Skill
|
|
183
186
|
|
|
184
|
-
Install the `/prq` skill to use
|
|
187
|
+
Install the `/prq` skill to use `prq` inside Claude Code sessions:
|
|
185
188
|
|
|
186
189
|
```bash
|
|
187
190
|
prq skill --global
|
package/dist/bin/prq.js
CHANGED
|
@@ -2081,8 +2081,8 @@ var require_commander = __commonJS((exports) => {
|
|
|
2081
2081
|
});
|
|
2082
2082
|
|
|
2083
2083
|
// src/cli.ts
|
|
2084
|
-
import
|
|
2085
|
-
import
|
|
2084
|
+
import fs4 from "node:fs";
|
|
2085
|
+
import path4 from "node:path";
|
|
2086
2086
|
import { fileURLToPath } from "node:url";
|
|
2087
2087
|
|
|
2088
2088
|
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
|
|
@@ -6664,8 +6664,59 @@ function categorize(reviewedPRs, requestedPRs, authoredPRs, staleDays) {
|
|
|
6664
6664
|
return results;
|
|
6665
6665
|
}
|
|
6666
6666
|
|
|
6667
|
+
// src/state.ts
|
|
6668
|
+
import fs2 from "node:fs";
|
|
6669
|
+
import path2 from "node:path";
|
|
6670
|
+
var STATE_DIR = path2.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".config", "prq");
|
|
6671
|
+
var STATE_PATH = path2.join(STATE_DIR, "in-progress.json");
|
|
6672
|
+
function prKey(pr) {
|
|
6673
|
+
return `${pr.repo}#${pr.number}`;
|
|
6674
|
+
}
|
|
6675
|
+
function loadInProgress() {
|
|
6676
|
+
try {
|
|
6677
|
+
const data = JSON.parse(fs2.readFileSync(STATE_PATH, "utf8"));
|
|
6678
|
+
return new Set(Array.isArray(data) ? data : []);
|
|
6679
|
+
} catch {
|
|
6680
|
+
return new Set;
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
function save(keys) {
|
|
6684
|
+
fs2.mkdirSync(STATE_DIR, { recursive: true });
|
|
6685
|
+
fs2.writeFileSync(STATE_PATH, JSON.stringify(Array.from(keys), null, 2));
|
|
6686
|
+
}
|
|
6687
|
+
function toggleInProgress(pr) {
|
|
6688
|
+
const keys = loadInProgress();
|
|
6689
|
+
const k = prKey(pr);
|
|
6690
|
+
if (keys.has(k)) {
|
|
6691
|
+
keys.delete(k);
|
|
6692
|
+
save(keys);
|
|
6693
|
+
return false;
|
|
6694
|
+
}
|
|
6695
|
+
keys.add(k);
|
|
6696
|
+
save(keys);
|
|
6697
|
+
return true;
|
|
6698
|
+
}
|
|
6699
|
+
function applyInProgress(prs) {
|
|
6700
|
+
const keys = loadInProgress();
|
|
6701
|
+
if (keys.size === 0)
|
|
6702
|
+
return prs;
|
|
6703
|
+
const activeKeys = new Set(prs.map(prKey));
|
|
6704
|
+
const staleKeys = Array.from(keys).filter((k) => !activeKeys.has(k));
|
|
6705
|
+
if (staleKeys.length > 0) {
|
|
6706
|
+
for (const k of staleKeys)
|
|
6707
|
+
keys.delete(k);
|
|
6708
|
+
save(keys);
|
|
6709
|
+
}
|
|
6710
|
+
return prs.map((pr) => keys.has(prKey(pr)) ? { ...pr, category: "in-progress" } : pr);
|
|
6711
|
+
}
|
|
6712
|
+
|
|
6667
6713
|
// src/interactive.ts
|
|
6668
6714
|
var CATEGORY_CONFIG = {
|
|
6715
|
+
"in-progress": {
|
|
6716
|
+
icon: "▸",
|
|
6717
|
+
label: "In Progress",
|
|
6718
|
+
color: source_default.cyan
|
|
6719
|
+
},
|
|
6669
6720
|
"needs-re-review": {
|
|
6670
6721
|
icon: "◆",
|
|
6671
6722
|
label: "Needs Re-review",
|
|
@@ -6680,6 +6731,7 @@ var CATEGORY_CONFIG = {
|
|
|
6680
6731
|
}
|
|
6681
6732
|
};
|
|
6682
6733
|
var CATEGORY_ORDER = [
|
|
6734
|
+
"in-progress",
|
|
6683
6735
|
"needs-re-review",
|
|
6684
6736
|
"requested",
|
|
6685
6737
|
"stale",
|
|
@@ -6752,8 +6804,10 @@ function render(state) {
|
|
|
6752
6804
|
lines.push("");
|
|
6753
6805
|
lines.push(` ${source_default.dim("1-9")} run action ${source_default.white("q")} back`);
|
|
6754
6806
|
} else {
|
|
6807
|
+
const pr = result.prs[selectedIndex];
|
|
6808
|
+
const sLabel = pr?.category === "in-progress" ? "stop" : "start";
|
|
6755
6809
|
lines.push("");
|
|
6756
|
-
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`);
|
|
6810
|
+
lines.push(` ${source_default.dim("↑↓")} navigate ${source_default.white("r")} review ${source_default.white("o")} open ${source_default.white("n")} nudge ${source_default.white("s")} ${sLabel} ${source_default.white("c")} copy url ${source_default.white("a")} actions ${source_default.white("q")} quit`);
|
|
6757
6811
|
}
|
|
6758
6812
|
if (message) {
|
|
6759
6813
|
lines.push("");
|
|
@@ -6793,7 +6847,7 @@ async function runAction(actionName, template, pr, state, onData) {
|
|
|
6793
6847
|
return source_default.red(`${actionName} failed`);
|
|
6794
6848
|
}
|
|
6795
6849
|
}
|
|
6796
|
-
async function interactiveMode(result, config) {
|
|
6850
|
+
async function interactiveMode(result, sourcePrs, config) {
|
|
6797
6851
|
if (result.prs.length === 0) {
|
|
6798
6852
|
console.log(source_default.green(`
|
|
6799
6853
|
All clear! No PRs need your attention.
|
|
@@ -6804,6 +6858,7 @@ async function interactiveMode(result, config) {
|
|
|
6804
6858
|
const allActions = listActions(config);
|
|
6805
6859
|
const state = {
|
|
6806
6860
|
result,
|
|
6861
|
+
sourcePrs,
|
|
6807
6862
|
selectedIndex: 0,
|
|
6808
6863
|
message: "",
|
|
6809
6864
|
actionMenu: null
|
|
@@ -6881,6 +6936,19 @@ async function interactiveMode(result, config) {
|
|
|
6881
6936
|
}
|
|
6882
6937
|
break;
|
|
6883
6938
|
}
|
|
6939
|
+
case "s": {
|
|
6940
|
+
const started = toggleInProgress(pr);
|
|
6941
|
+
state.result = {
|
|
6942
|
+
...state.result,
|
|
6943
|
+
prs: applyInProgress(state.sourcePrs)
|
|
6944
|
+
};
|
|
6945
|
+
const newTotal = state.result.prs.length;
|
|
6946
|
+
if (state.selectedIndex >= newTotal) {
|
|
6947
|
+
state.selectedIndex = Math.max(0, newTotal - 1);
|
|
6948
|
+
}
|
|
6949
|
+
state.message = started ? source_default.cyan(`started: ${pr.repo}#${pr.number}`) : source_default.dim(`unmarked: ${pr.repo}#${pr.number}`);
|
|
6950
|
+
break;
|
|
6951
|
+
}
|
|
6884
6952
|
case "a": {
|
|
6885
6953
|
const entries = Object.entries(allActions);
|
|
6886
6954
|
state.actionMenu = entries.map(([name, template]) => ({
|
|
@@ -6901,6 +6969,11 @@ async function interactiveMode(result, config) {
|
|
|
6901
6969
|
|
|
6902
6970
|
// src/output.ts
|
|
6903
6971
|
var CATEGORY_CONFIG2 = {
|
|
6972
|
+
"in-progress": {
|
|
6973
|
+
icon: "▸",
|
|
6974
|
+
label: "In Progress",
|
|
6975
|
+
color: source_default.cyan
|
|
6976
|
+
},
|
|
6904
6977
|
"needs-re-review": {
|
|
6905
6978
|
icon: "◆",
|
|
6906
6979
|
label: "Needs Re-review",
|
|
@@ -6915,6 +6988,7 @@ var CATEGORY_CONFIG2 = {
|
|
|
6915
6988
|
}
|
|
6916
6989
|
};
|
|
6917
6990
|
var CATEGORY_ORDER2 = [
|
|
6991
|
+
"in-progress",
|
|
6918
6992
|
"needs-re-review",
|
|
6919
6993
|
"requested",
|
|
6920
6994
|
"stale",
|
|
@@ -6984,7 +7058,8 @@ async function statusCommand(config, json, interactive) {
|
|
|
6984
7058
|
process.stderr.write(`Found ${reviewedRaw.length} reviewed, ${requestedPRs.length} requested, ${authoredPRs.length} authored
|
|
6985
7059
|
`);
|
|
6986
7060
|
const reviewedPRs = await enrichAllWithReviews(reviewedRaw, user);
|
|
6987
|
-
const
|
|
7061
|
+
const categorized = categorize(reviewedPRs, requestedPRs, authoredPRs, config.staleDays);
|
|
7062
|
+
const prs = applyInProgress(categorized);
|
|
6988
7063
|
const result = {
|
|
6989
7064
|
user,
|
|
6990
7065
|
timestamp: new Date().toISOString(),
|
|
@@ -6994,15 +7069,15 @@ async function statusCommand(config, json, interactive) {
|
|
|
6994
7069
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
6995
7070
|
`);
|
|
6996
7071
|
} else if (interactive && process.stdin.isTTY) {
|
|
6997
|
-
await interactiveMode(result, config);
|
|
7072
|
+
await interactiveMode(result, categorized, config);
|
|
6998
7073
|
} else {
|
|
6999
7074
|
process.stdout.write(formatStatus(result));
|
|
7000
7075
|
}
|
|
7001
7076
|
}
|
|
7002
7077
|
|
|
7003
7078
|
// src/config.ts
|
|
7004
|
-
import
|
|
7005
|
-
import
|
|
7079
|
+
import fs3 from "node:fs";
|
|
7080
|
+
import path3 from "node:path";
|
|
7006
7081
|
|
|
7007
7082
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
7008
7083
|
var exports_external = {};
|
|
@@ -7479,8 +7554,8 @@ function getErrorMap() {
|
|
|
7479
7554
|
}
|
|
7480
7555
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
7481
7556
|
var makeIssue = (params) => {
|
|
7482
|
-
const { data, path:
|
|
7483
|
-
const fullPath = [...
|
|
7557
|
+
const { data, path: path3, errorMaps, issueData } = params;
|
|
7558
|
+
const fullPath = [...path3, ...issueData.path || []];
|
|
7484
7559
|
const fullIssue = {
|
|
7485
7560
|
...issueData,
|
|
7486
7561
|
path: fullPath
|
|
@@ -7592,11 +7667,11 @@ var errorUtil;
|
|
|
7592
7667
|
|
|
7593
7668
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
7594
7669
|
class ParseInputLazyPath {
|
|
7595
|
-
constructor(parent, value,
|
|
7670
|
+
constructor(parent, value, path3, key) {
|
|
7596
7671
|
this._cachedPath = [];
|
|
7597
7672
|
this.parent = parent;
|
|
7598
7673
|
this.data = value;
|
|
7599
|
-
this._path =
|
|
7674
|
+
this._path = path3;
|
|
7600
7675
|
this._key = key;
|
|
7601
7676
|
}
|
|
7602
7677
|
get path() {
|
|
@@ -10986,14 +11061,14 @@ var configSchema = exports_external.object({
|
|
|
10986
11061
|
});
|
|
10987
11062
|
function loadConfig(cliOverrides) {
|
|
10988
11063
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10989
|
-
const globalPath =
|
|
10990
|
-
const localPath =
|
|
11064
|
+
const globalPath = path3.join(home, ".config", "prq", "config.json");
|
|
11065
|
+
const localPath = path3.join(process.cwd(), ".prqrc.json");
|
|
10991
11066
|
let config = {};
|
|
10992
|
-
if (
|
|
10993
|
-
config = JSON.parse(
|
|
11067
|
+
if (fs3.existsSync(globalPath)) {
|
|
11068
|
+
config = JSON.parse(fs3.readFileSync(globalPath, "utf8"));
|
|
10994
11069
|
}
|
|
10995
|
-
if (
|
|
10996
|
-
config = { ...config, ...JSON.parse(
|
|
11070
|
+
if (fs3.existsSync(localPath)) {
|
|
11071
|
+
config = { ...config, ...JSON.parse(fs3.readFileSync(localPath, "utf8")) };
|
|
10997
11072
|
}
|
|
10998
11073
|
for (const [key, value] of Object.entries(cliOverrides)) {
|
|
10999
11074
|
if (value !== undefined) {
|
|
@@ -11005,11 +11080,11 @@ function loadConfig(cliOverrides) {
|
|
|
11005
11080
|
|
|
11006
11081
|
// src/cli.ts
|
|
11007
11082
|
function getVersion() {
|
|
11008
|
-
const __dirname2 =
|
|
11083
|
+
const __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
|
|
11009
11084
|
for (const rel of ["../../package.json", "../package.json"]) {
|
|
11010
|
-
const p =
|
|
11011
|
-
if (
|
|
11012
|
-
return JSON.parse(
|
|
11085
|
+
const p = path4.resolve(__dirname2, rel);
|
|
11086
|
+
if (fs4.existsSync(p)) {
|
|
11087
|
+
return JSON.parse(fs4.readFileSync(p, "utf8")).version;
|
|
11013
11088
|
}
|
|
11014
11089
|
}
|
|
11015
11090
|
return "0.0.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prq-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "PR Queue — see what code reviews need your attention",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"type": "git",
|
|
38
38
|
"url": "git+https://github.com/caiopizzol/prq.git"
|
|
39
39
|
},
|
|
40
|
-
"homepage": "https://
|
|
40
|
+
"homepage": "https://prq.sh",
|
|
41
41
|
"bugs": "https://github.com/caiopizzol/prq/issues"
|
|
42
42
|
}
|