prq-cli 0.7.0 → 0.8.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
@@ -95,19 +95,21 @@ prq skill --global # install globally
95
95
 
96
96
  ## Pluggable Actions
97
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`:
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`.
99
+
100
+ 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.
99
101
 
100
102
  ### Use Claude Code for reviews
101
103
 
102
104
  ```json
103
105
  {
104
106
  "actions": {
105
- "review": "claude -p '/review {url}'"
107
+ "review": "claude '/review {url}'"
106
108
  }
107
109
  }
108
110
  ```
109
111
 
110
- Now `prq review 482` dispatches to Claude Code.
112
+ Now `prq review 482` opens an interactive Claude Code session.
111
113
 
112
114
  ### Use Codex for reviews
113
115
 
@@ -119,6 +121,18 @@ Now `prq review 482` dispatches to Claude Code.
119
121
  }
120
122
  ```
121
123
 
124
+ ### Use a script for complex workflows
125
+
126
+ ```json
127
+ {
128
+ "actions": {
129
+ "review": "./scripts/review.sh {number} {url}"
130
+ }
131
+ }
132
+ ```
133
+
134
+ The script handles its own logic — session management, resuming, branching, whatever you need.
135
+
122
136
  ### Use gh CLI to checkout
123
137
 
124
138
  ```json
@@ -143,9 +157,11 @@ With no config, `prq review` opens the files changed tab and `prq open` opens th
143
157
  | `{number}` | `482` |
144
158
  | `{owner}` | `org` |
145
159
  | `{repo}` | `repo` |
160
+ | `{fullRepo}` | `org/repo` |
146
161
  | `{title}` | `fix: handle edge case` |
147
162
  | `{author}` | `alice` |
148
163
  | `{days}` | `5` |
164
+ | `{category}` | `needs-re-review` |
149
165
 
150
166
  ## Agent & Automation
151
167
 
@@ -196,7 +212,7 @@ Full example:
196
212
  "repos": ["org/repo1", "org/repo2"],
197
213
  "staleDays": 5,
198
214
  "actions": {
199
- "review": "claude -p '/review {url}'",
215
+ "review": "claude '/review {url}'",
200
216
  "checkout": "gh pr checkout {number} --repo {owner}/{repo}",
201
217
  "approve": "gh pr review {number} --repo {owner}/{repo} --approve"
202
218
  }
package/dist/bin/prq.js CHANGED
@@ -6424,7 +6424,7 @@ function getAction(name, config) {
6424
6424
  function listActions(config) {
6425
6425
  return { ...DEFAULT_ACTIONS, ...config.actions };
6426
6426
  }
6427
- function buildContext(pr) {
6427
+ function buildContext(pr, category = "") {
6428
6428
  const days = Math.floor((Date.now() - new Date(pr.updatedAt || Date.now()).getTime()) / 86400000);
6429
6429
  return {
6430
6430
  url: pr.url,
@@ -6434,7 +6434,8 @@ function buildContext(pr) {
6434
6434
  fullRepo: `${pr.owner}/${pr.repo}`,
6435
6435
  title: pr.title,
6436
6436
  author: pr.author,
6437
- days
6437
+ days,
6438
+ category
6438
6439
  };
6439
6440
  }
6440
6441
  function interpolate(template, context) {
@@ -6797,14 +6798,34 @@ function render(state) {
6797
6798
  process.stdout.write(lines.join(`
6798
6799
  `));
6799
6800
  }
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}...`);
6801
+ function suspend() {
6802
+ process.stdin.setRawMode(false);
6803
+ process.stdin.pause();
6804
+ process.stdin.removeAllListeners("data");
6805
+ process.stdout.write("\x1B[?25h\x1B[2J\x1B[H");
6806
+ }
6807
+ function resume(state, onData) {
6808
+ process.stdin.setRawMode(true);
6809
+ process.stdin.resume();
6810
+ process.stdin.setEncoding("utf8");
6811
+ process.stdout.write("\x1B[?25l");
6812
+ process.stdin.on("data", onData);
6803
6813
  render(state);
6814
+ }
6815
+ async function runAction(actionName, template, pr, state, onData) {
6816
+ const context = buildContext(toResolvedPR(pr), pr.category);
6817
+ const cmd = interpolate(template, context);
6818
+ suspend();
6819
+ process.stdout.write(source_default.dim(`
6820
+ running ${actionName} on ${pr.repo}#${pr.number}...
6821
+
6822
+ `));
6804
6823
  try {
6805
6824
  await executeCommand(cmd);
6825
+ resume(state, onData);
6806
6826
  return source_default.green(`${actionName}: ${pr.repo}#${pr.number}`);
6807
6827
  } catch {
6828
+ resume(state, onData);
6808
6829
  return source_default.red(`${actionName} failed`);
6809
6830
  }
6810
6831
  }
@@ -6828,33 +6849,22 @@ async function interactiveMode(result, config) {
6828
6849
  `);
6829
6850
  return;
6830
6851
  }
6831
- process.stdin.setRawMode(true);
6832
- process.stdin.resume();
6833
- process.stdin.setEncoding("utf8");
6834
- process.stdout.write("\x1B[?25l");
6835
- render(state);
6836
6852
  return new Promise((resolve) => {
6837
- const cleanup = () => {
6838
- process.stdin.setRawMode(false);
6839
- process.stdin.pause();
6840
- process.stdin.removeAllListeners("data");
6841
- process.stdout.write("\x1B[?25h\x1B[2J\x1B[H");
6842
- };
6843
- process.stdin.on("data", async (key) => {
6853
+ const onData = async (key) => {
6844
6854
  const pr = result.prs[state.selectedIndex];
6845
6855
  if (state.actionMenu) {
6846
6856
  if (key === "q" || key === "\x1B" || key === "a") {
6847
6857
  state.actionMenu = null;
6848
6858
  state.message = "";
6849
6859
  } else if (key === "\x03") {
6850
- cleanup();
6860
+ suspend();
6851
6861
  resolve();
6852
6862
  return;
6853
6863
  } else {
6854
6864
  const idx = parseInt(key, 10);
6855
6865
  if (idx >= 1 && idx <= state.actionMenu.length) {
6856
6866
  const action = state.actionMenu[idx - 1];
6857
- state.message = await runAction(action.name, action.template, pr, state);
6867
+ state.message = await runAction(action.name, action.template, pr, state, onData);
6858
6868
  state.actionMenu = null;
6859
6869
  }
6860
6870
  }
@@ -6864,7 +6874,7 @@ async function interactiveMode(result, config) {
6864
6874
  switch (key) {
6865
6875
  case "q":
6866
6876
  case "\x03":
6867
- cleanup();
6877
+ suspend();
6868
6878
  resolve();
6869
6879
  return;
6870
6880
  case "\x1B[A":
@@ -6878,21 +6888,21 @@ async function interactiveMode(result, config) {
6878
6888
  case "o": {
6879
6889
  const template = allActions.open;
6880
6890
  if (template) {
6881
- state.message = await runAction("open", template, pr, state);
6891
+ state.message = await runAction("open", template, pr, state, onData);
6882
6892
  }
6883
6893
  break;
6884
6894
  }
6885
6895
  case "r": {
6886
6896
  const template = allActions.review;
6887
6897
  if (template) {
6888
- state.message = await runAction("review", template, pr, state);
6898
+ state.message = await runAction("review", template, pr, state, onData);
6889
6899
  }
6890
6900
  break;
6891
6901
  }
6892
6902
  case "n": {
6893
6903
  const template = allActions.nudge;
6894
6904
  if (template) {
6895
- state.message = await runAction("nudge", template, pr, state);
6905
+ state.message = await runAction("nudge", template, pr, state, onData);
6896
6906
  }
6897
6907
  break;
6898
6908
  }
@@ -6920,7 +6930,8 @@ async function interactiveMode(result, config) {
6920
6930
  break;
6921
6931
  }
6922
6932
  render(state);
6923
- });
6933
+ };
6934
+ resume(state, onData);
6924
6935
  });
6925
6936
  }
6926
6937
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prq-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "PR Queue — see what code reviews need your attention",
5
5
  "type": "module",
6
6
  "bin": {