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 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 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.
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
- ## Pluggable Actions
99
+ ## Custom Actions
97
100
 
98
- 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`.
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
- ## Agent & Automation
169
+ ## Scripting
167
170
 
168
- PRQ is fully scriptable with `--json` output and `--yes` flags:
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 PRQ inside Claude Code sessions:
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 fs3 from "node:fs";
2085
- import path3 from "node:path";
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 prs = categorize(reviewedPRs, requestedPRs, authoredPRs, config.staleDays);
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 fs2 from "node:fs";
7005
- import path2 from "node:path";
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: path2, errorMaps, issueData } = params;
7483
- const fullPath = [...path2, ...issueData.path || []];
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, path2, key) {
7670
+ constructor(parent, value, path3, key) {
7596
7671
  this._cachedPath = [];
7597
7672
  this.parent = parent;
7598
7673
  this.data = value;
7599
- this._path = path2;
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 = path2.join(home, ".config", "prq", "config.json");
10990
- const localPath = path2.join(process.cwd(), ".prqrc.json");
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 (fs2.existsSync(globalPath)) {
10993
- config = JSON.parse(fs2.readFileSync(globalPath, "utf8"));
11067
+ if (fs3.existsSync(globalPath)) {
11068
+ config = JSON.parse(fs3.readFileSync(globalPath, "utf8"));
10994
11069
  }
10995
- if (fs2.existsSync(localPath)) {
10996
- config = { ...config, ...JSON.parse(fs2.readFileSync(localPath, "utf8")) };
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 = path3.dirname(fileURLToPath(import.meta.url));
11083
+ const __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
11009
11084
  for (const rel of ["../../package.json", "../package.json"]) {
11010
- const p = path3.resolve(__dirname2, rel);
11011
- if (fs3.existsSync(p)) {
11012
- return JSON.parse(fs3.readFileSync(p, "utf8")).version;
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.9.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://github.com/caiopizzol/prq",
40
+ "homepage": "https://prq.sh",
41
41
  "bugs": "https://github.com/caiopizzol/prq/issues"
42
42
  }