prq-cli 0.1.4 → 0.2.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.
Files changed (4) hide show
  1. package/README.md +20 -10
  2. package/dist/bin/prq.js +328 -149
  3. package/package.json +6 -18
  4. package/LICENSE +0 -21
package/README.md CHANGED
@@ -42,6 +42,26 @@ prq status --stale-days 7 # custom stale threshold
42
42
  prq status --json # machine-readable output
43
43
  ```
44
44
 
45
+ ### `prq open <identifier>`
46
+
47
+ Open a PR in the browser. Accepts a PR number, `org/repo#number`, or a full GitHub URL.
48
+
49
+ ```bash
50
+ prq open 482 # searches your queue for PR #482
51
+ prq open superdoc-dev/superdoc#482 # opens directly
52
+ prq open https://github.com/org/repo/pull/482
53
+ ```
54
+
55
+ ### `prq nudge <identifier>`
56
+
57
+ Post a comment on a PR asking if it's still active.
58
+
59
+ ```bash
60
+ prq nudge 482 # confirm before posting
61
+ prq nudge 482 --yes # skip confirmation
62
+ prq nudge 482 --message "Any updates?" # custom message
63
+ ```
64
+
45
65
  ### `prq init`
46
66
 
47
67
  Creates a `.prqrc.json` config file in the current directory.
@@ -67,16 +87,6 @@ Example `.prqrc.json`:
67
87
  }
68
88
  ```
69
89
 
70
- ## Development
71
-
72
- ```bash
73
- bun install
74
- bun run dev # run the CLI
75
- bun test # run tests
76
- bun run lint # check lint + formatting
77
- bun run typecheck # type check
78
- ```
79
-
80
90
  ## License
81
91
 
82
92
  MIT
package/dist/bin/prq.js CHANGED
@@ -46,7 +46,7 @@ var __export = (target, all) => {
46
46
  };
47
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
48
 
49
- // node_modules/commander/lib/error.js
49
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
50
50
  var require_error = __commonJS((exports) => {
51
51
  class CommanderError extends Error {
52
52
  constructor(exitCode, code, message) {
@@ -70,7 +70,7 @@ var require_error = __commonJS((exports) => {
70
70
  exports.InvalidArgumentError = InvalidArgumentError;
71
71
  });
72
72
 
73
- // node_modules/commander/lib/argument.js
73
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
74
74
  var require_argument = __commonJS((exports) => {
75
75
  var { InvalidArgumentError } = require_error();
76
76
 
@@ -149,7 +149,7 @@ var require_argument = __commonJS((exports) => {
149
149
  exports.humanReadableArgName = humanReadableArgName;
150
150
  });
151
151
 
152
- // node_modules/commander/lib/help.js
152
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
153
153
  var require_help = __commonJS((exports) => {
154
154
  var { humanReadableArgName } = require_argument();
155
155
 
@@ -499,7 +499,7 @@ ${itemIndentStr}`);
499
499
  exports.stripColor = stripColor;
500
500
  });
501
501
 
502
- // node_modules/commander/lib/option.js
502
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
503
503
  var require_option = __commonJS((exports) => {
504
504
  var { InvalidArgumentError } = require_error();
505
505
 
@@ -677,7 +677,7 @@ var require_option = __commonJS((exports) => {
677
677
  exports.DualOptions = DualOptions;
678
678
  });
679
679
 
680
- // node_modules/commander/lib/suggestSimilar.js
680
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
681
681
  var require_suggestSimilar = __commonJS((exports) => {
682
682
  var maxDistance = 3;
683
683
  function editDistance(a, b) {
@@ -750,7 +750,7 @@ var require_suggestSimilar = __commonJS((exports) => {
750
750
  exports.suggestSimilar = suggestSimilar;
751
751
  });
752
752
 
753
- // node_modules/commander/lib/command.js
753
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
754
754
  var require_command = __commonJS((exports) => {
755
755
  var EventEmitter = __require("node:events").EventEmitter;
756
756
  var childProcess = __require("node:child_process");
@@ -2060,7 +2060,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2060
2060
  exports.useColor = useColor;
2061
2061
  });
2062
2062
 
2063
- // node_modules/commander/index.js
2063
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
2064
2064
  var require_commander = __commonJS((exports) => {
2065
2065
  var { Argument } = require_argument();
2066
2066
  var { Command } = require_command();
@@ -2085,7 +2085,7 @@ import fs3 from "node:fs";
2085
2085
  import path3 from "node:path";
2086
2086
  import { fileURLToPath } from "node:url";
2087
2087
 
2088
- // node_modules/commander/esm.mjs
2088
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
2089
2089
  var import__ = __toESM(require_commander(), 1);
2090
2090
  var {
2091
2091
  program,
@@ -2106,7 +2106,7 @@ import fs from "node:fs";
2106
2106
  import path from "node:path";
2107
2107
  import readline from "node:readline";
2108
2108
 
2109
- // node_modules/chalk/source/vendor/ansi-styles/index.js
2109
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
2110
2110
  var ANSI_BACKGROUND_OFFSET = 10;
2111
2111
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
2112
2112
  var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
@@ -2283,7 +2283,7 @@ function assembleStyles() {
2283
2283
  var ansiStyles = assembleStyles();
2284
2284
  var ansi_styles_default = ansiStyles;
2285
2285
 
2286
- // node_modules/chalk/source/vendor/supports-color/index.js
2286
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
2287
2287
  import process2 from "node:process";
2288
2288
  import os from "node:os";
2289
2289
  import tty from "node:tty";
@@ -2415,7 +2415,7 @@ var supportsColor = {
2415
2415
  };
2416
2416
  var supports_color_default = supportsColor;
2417
2417
 
2418
- // node_modules/chalk/source/utilities.js
2418
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/utilities.js
2419
2419
  function stringReplaceAll(string, substring, replacer) {
2420
2420
  let index = string.indexOf(substring);
2421
2421
  if (index === -1) {
@@ -2448,7 +2448,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
2448
2448
  return returnValue;
2449
2449
  }
2450
2450
 
2451
- // node_modules/chalk/source/index.js
2451
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/index.js
2452
2452
  var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
2453
2453
  var GENERATOR = Symbol("GENERATOR");
2454
2454
  var STYLER = Symbol("STYLER");
@@ -2598,7 +2598,7 @@ var source_default = chalk;
2598
2598
  // src/github/client.ts
2599
2599
  import { execSync } from "node:child_process";
2600
2600
 
2601
- // node_modules/universal-user-agent/index.js
2601
+ // ../../node_modules/.bun/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
2602
2602
  function getUserAgent() {
2603
2603
  if (typeof navigator === "object" && "userAgent" in navigator) {
2604
2604
  return navigator.userAgent;
@@ -2609,7 +2609,7 @@ function getUserAgent() {
2609
2609
  return "<environment undetectable>";
2610
2610
  }
2611
2611
 
2612
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/before-after-hook/lib/register.js
2612
+ // ../../node_modules/.bun/before-after-hook@3.0.2/node_modules/before-after-hook/lib/register.js
2613
2613
  function register(state, name, method, options) {
2614
2614
  if (typeof method !== "function") {
2615
2615
  throw new Error("method for before hook must be a function");
@@ -2632,7 +2632,7 @@ function register(state, name, method, options) {
2632
2632
  });
2633
2633
  }
2634
2634
 
2635
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/before-after-hook/lib/add.js
2635
+ // ../../node_modules/.bun/before-after-hook@3.0.2/node_modules/before-after-hook/lib/add.js
2636
2636
  function addHook(state, kind, name, hook) {
2637
2637
  const orig = hook;
2638
2638
  if (!state.registry[name]) {
@@ -2667,7 +2667,7 @@ function addHook(state, kind, name, hook) {
2667
2667
  });
2668
2668
  }
2669
2669
 
2670
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/before-after-hook/lib/remove.js
2670
+ // ../../node_modules/.bun/before-after-hook@3.0.2/node_modules/before-after-hook/lib/remove.js
2671
2671
  function removeHook(state, name, method) {
2672
2672
  if (!state.registry[name]) {
2673
2673
  return;
@@ -2681,7 +2681,7 @@ function removeHook(state, name, method) {
2681
2681
  state.registry[name].splice(index, 1);
2682
2682
  }
2683
2683
 
2684
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/before-after-hook/index.js
2684
+ // ../../node_modules/.bun/before-after-hook@3.0.2/node_modules/before-after-hook/index.js
2685
2685
  var bind = Function.bind;
2686
2686
  var bindable = bind.bind(bind);
2687
2687
  function bindApi(hook, state, name) {
@@ -2712,7 +2712,7 @@ function Collection() {
2712
2712
  }
2713
2713
  var before_after_hook_default = { Singular, Collection };
2714
2714
 
2715
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/request/node_modules/@octokit/endpoint/dist-bundle/index.js
2715
+ // ../../node_modules/.bun/@octokit+endpoint@10.1.4/node_modules/@octokit/endpoint/dist-bundle/index.js
2716
2716
  var VERSION = "0.0.0-development";
2717
2717
  var userAgent = `octokit-endpoint.js/${VERSION} ${getUserAgent()}`;
2718
2718
  var DEFAULTS = {
@@ -3012,7 +3012,7 @@ function withDefaults(oldDefaults, newDefaults) {
3012
3012
  }
3013
3013
  var endpoint = withDefaults(null, DEFAULTS);
3014
3014
 
3015
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/request/node_modules/fast-content-type-parse/index.js
3015
+ // ../../node_modules/.bun/fast-content-type-parse@2.0.1/node_modules/fast-content-type-parse/index.js
3016
3016
  var NullObject = function NullObject2() {};
3017
3017
  NullObject.prototype = Object.create(null);
3018
3018
  var paramRE = /; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu;
@@ -3061,7 +3061,7 @@ function safeParse(header) {
3061
3061
  }
3062
3062
  var $safeParse = safeParse;
3063
3063
 
3064
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/request-error/dist-src/index.js
3064
+ // ../../node_modules/.bun/@octokit+request-error@6.1.8/node_modules/@octokit/request-error/dist-src/index.js
3065
3065
  class RequestError extends Error {
3066
3066
  name;
3067
3067
  status;
@@ -3088,7 +3088,7 @@ class RequestError extends Error {
3088
3088
  }
3089
3089
  }
3090
3090
 
3091
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/request/dist-bundle/index.js
3091
+ // ../../node_modules/.bun/@octokit+request@9.2.4/node_modules/@octokit/request/dist-bundle/index.js
3092
3092
  var VERSION2 = "9.2.4";
3093
3093
  var defaults_default = {
3094
3094
  headers: {
@@ -3255,7 +3255,7 @@ function withDefaults2(oldEndpoint, newDefaults) {
3255
3255
  }
3256
3256
  var request = withDefaults2(endpoint, defaults_default);
3257
3257
 
3258
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/graphql/dist-bundle/index.js
3258
+ // ../../node_modules/.bun/@octokit+graphql@8.2.2/node_modules/@octokit/graphql/dist-bundle/index.js
3259
3259
  var VERSION3 = "0.0.0-development";
3260
3260
  function _buildMessageForResponseErrors(data) {
3261
3261
  return `Request failed due to following response errors:
@@ -3352,7 +3352,7 @@ function withCustomRequest(customRequest) {
3352
3352
  });
3353
3353
  }
3354
3354
 
3355
- // node_modules/@octokit/rest/node_modules/@octokit/core/node_modules/@octokit/auth-token/dist-bundle/index.js
3355
+ // ../../node_modules/.bun/@octokit+auth-token@5.1.2/node_modules/@octokit/auth-token/dist-bundle/index.js
3356
3356
  var b64url = "(?:[a-zA-Z0-9_-]+)";
3357
3357
  var sep = "\\.";
3358
3358
  var jwtRE = new RegExp(`^${b64url}${sep}${b64url}${sep}${b64url}$`);
@@ -3392,10 +3392,10 @@ var createTokenAuth = function createTokenAuth2(token) {
3392
3392
  });
3393
3393
  };
3394
3394
 
3395
- // node_modules/@octokit/rest/node_modules/@octokit/core/dist-src/version.js
3395
+ // ../../node_modules/.bun/@octokit+core@6.1.6/node_modules/@octokit/core/dist-src/version.js
3396
3396
  var VERSION4 = "6.1.6";
3397
3397
 
3398
- // node_modules/@octokit/rest/node_modules/@octokit/core/dist-src/index.js
3398
+ // ../../node_modules/.bun/@octokit+core@6.1.6/node_modules/@octokit/core/dist-src/index.js
3399
3399
  var noop = () => {};
3400
3400
  var consoleWarn = console.warn.bind(console);
3401
3401
  var consoleError = console.error.bind(console);
@@ -3501,10 +3501,10 @@ class Octokit {
3501
3501
  auth;
3502
3502
  }
3503
3503
 
3504
- // node_modules/@octokit/plugin-request-log/dist-src/version.js
3504
+ // ../../node_modules/.bun/@octokit+plugin-request-log@5.3.1+4d155d08142e7639/node_modules/@octokit/plugin-request-log/dist-src/version.js
3505
3505
  var VERSION5 = "5.3.1";
3506
3506
 
3507
- // node_modules/@octokit/plugin-request-log/dist-src/index.js
3507
+ // ../../node_modules/.bun/@octokit+plugin-request-log@5.3.1+4d155d08142e7639/node_modules/@octokit/plugin-request-log/dist-src/index.js
3508
3508
  function requestLog(octokit) {
3509
3509
  octokit.hook.wrap("request", (request2, options) => {
3510
3510
  octokit.log.debug("request", options);
@@ -3524,7 +3524,7 @@ function requestLog(octokit) {
3524
3524
  }
3525
3525
  requestLog.VERSION = VERSION5;
3526
3526
 
3527
- // node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/dist-bundle/index.js
3527
+ // ../../node_modules/.bun/@octokit+plugin-paginate-rest@11.6.0+4d155d08142e7639/node_modules/@octokit/plugin-paginate-rest/dist-bundle/index.js
3528
3528
  var VERSION6 = "0.0.0-development";
3529
3529
  function normalizePaginatedListResponse(response) {
3530
3530
  if (!response.data) {
@@ -3621,10 +3621,10 @@ function paginateRest(octokit) {
3621
3621
  }
3622
3622
  paginateRest.VERSION = VERSION6;
3623
3623
 
3624
- // node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/version.js
3624
+ // ../../node_modules/.bun/@octokit+plugin-rest-endpoint-methods@13.5.0+4d155d08142e7639/node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/version.js
3625
3625
  var VERSION7 = "13.5.0";
3626
3626
 
3627
- // node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/generated/endpoints.js
3627
+ // ../../node_modules/.bun/@octokit+plugin-rest-endpoint-methods@13.5.0+4d155d08142e7639/node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/generated/endpoints.js
3628
3628
  var Endpoints = {
3629
3629
  actions: {
3630
3630
  addCustomLabelsToSelfHostedRunnerForOrg: [
@@ -5953,7 +5953,7 @@ var Endpoints = {
5953
5953
  };
5954
5954
  var endpoints_default = Endpoints;
5955
5955
 
5956
- // node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/endpoints-to-methods.js
5956
+ // ../../node_modules/.bun/@octokit+plugin-rest-endpoint-methods@13.5.0+4d155d08142e7639/node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/endpoints-to-methods.js
5957
5957
  var endpointMethodsMap = /* @__PURE__ */ new Map;
5958
5958
  for (const [scope, endpoints] of Object.entries(endpoints_default)) {
5959
5959
  for (const [methodName, endpoint2] of Object.entries(endpoints)) {
@@ -6060,7 +6060,7 @@ function decorate(octokit, scope, methodName, defaults, decorations) {
6060
6060
  return Object.assign(withDecorations, requestWithDefaults);
6061
6061
  }
6062
6062
 
6063
- // node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/index.js
6063
+ // ../../node_modules/.bun/@octokit+plugin-rest-endpoint-methods@13.5.0+4d155d08142e7639/node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/index.js
6064
6064
  function restEndpointMethods(octokit) {
6065
6065
  const api = endpointsToMethods(octokit);
6066
6066
  return {
@@ -6077,10 +6077,10 @@ function legacyRestEndpointMethods(octokit) {
6077
6077
  }
6078
6078
  legacyRestEndpointMethods.VERSION = VERSION7;
6079
6079
 
6080
- // node_modules/@octokit/rest/dist-src/version.js
6080
+ // ../../node_modules/.bun/@octokit+rest@21.1.1/node_modules/@octokit/rest/dist-src/version.js
6081
6081
  var VERSION8 = "21.1.1";
6082
6082
 
6083
- // node_modules/@octokit/rest/dist-src/index.js
6083
+ // ../../node_modules/.bun/@octokit+rest@21.1.1/node_modules/@octokit/rest/dist-src/index.js
6084
6084
  var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRest).defaults({
6085
6085
  userAgent: `octokit-rest.js/${VERSION8}`
6086
6086
  });
@@ -6161,110 +6161,8 @@ async function initCommand() {
6161
6161
  }
6162
6162
  }
6163
6163
 
6164
- // src/categorize.ts
6165
- function timeAgo(dateStr) {
6166
- const now = Date.now();
6167
- const then = new Date(dateStr).getTime();
6168
- const diffMs = now - then;
6169
- const diffMins = Math.floor(diffMs / 60000);
6170
- const diffHours = Math.floor(diffMs / 3600000);
6171
- const diffDays = Math.floor(diffMs / 86400000);
6172
- if (diffMins < 60)
6173
- return `${diffMins}m ago`;
6174
- if (diffHours < 24)
6175
- return `${diffHours}h ago`;
6176
- return `${diffDays}d ago`;
6177
- }
6178
- function daysAgo(dateStr) {
6179
- return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
6180
- }
6181
- function categorize(reviewedPRs, requestedPRs, authoredPRs, staleDays) {
6182
- const results = [];
6183
- const seen = new Set;
6184
- const key = (pr) => `${pr.repo}#${pr.number}`;
6185
- const requestedKeys = new Set(requestedPRs.map(key));
6186
- for (const pr of reviewedPRs) {
6187
- const k = key(pr);
6188
- if (seen.has(k))
6189
- continue;
6190
- const reviewDate = new Date(pr.userLastReviewedAt).getTime();
6191
- const commitDate = new Date(pr.latestCommitAt).getTime();
6192
- if (commitDate > reviewDate) {
6193
- seen.add(k);
6194
- results.push({
6195
- category: "needs-re-review",
6196
- repo: pr.repo,
6197
- number: pr.number,
6198
- title: pr.title,
6199
- author: pr.author,
6200
- url: pr.url,
6201
- isDraft: pr.isDraft,
6202
- updatedAt: pr.updatedAt,
6203
- detail: `New commits since your review ${timeAgo(pr.userLastReviewedAt)}`
6204
- });
6205
- }
6206
- }
6207
- for (const pr of requestedPRs) {
6208
- const k = key(pr);
6209
- if (seen.has(k))
6210
- continue;
6211
- seen.add(k);
6212
- results.push({
6213
- category: "requested",
6214
- repo: pr.repo,
6215
- number: pr.number,
6216
- title: pr.title,
6217
- author: pr.author,
6218
- url: pr.url,
6219
- isDraft: pr.isDraft,
6220
- updatedAt: pr.updatedAt,
6221
- detail: `Requested ${timeAgo(pr.updatedAt)}`
6222
- });
6223
- }
6224
- for (const pr of reviewedPRs) {
6225
- const k = key(pr);
6226
- if (seen.has(k))
6227
- continue;
6228
- if (requestedKeys.has(k))
6229
- continue;
6230
- const inactive = daysAgo(pr.updatedAt);
6231
- if (inactive >= staleDays) {
6232
- seen.add(k);
6233
- results.push({
6234
- category: "stale",
6235
- repo: pr.repo,
6236
- number: pr.number,
6237
- title: pr.title,
6238
- author: pr.author,
6239
- url: pr.url,
6240
- isDraft: pr.isDraft,
6241
- updatedAt: pr.updatedAt,
6242
- detail: `No activity for ${inactive} days`
6243
- });
6244
- }
6245
- }
6246
- for (const pr of authoredPRs) {
6247
- const k = key(pr);
6248
- if (seen.has(k))
6249
- continue;
6250
- if (pr.requestedReviewers.length > 0) {
6251
- seen.add(k);
6252
- const reviewers = pr.requestedReviewers.map((r) => `@${r}`).join(", ");
6253
- results.push({
6254
- category: "waiting-on-others",
6255
- repo: pr.repo,
6256
- number: pr.number,
6257
- title: pr.title,
6258
- author: pr.author,
6259
- url: pr.url,
6260
- isDraft: pr.isDraft,
6261
- updatedAt: pr.updatedAt,
6262
- detail: `Waiting on review from ${reviewers}`
6263
- });
6264
- }
6265
- }
6266
- return results;
6267
- }
6164
+ // src/commands/nudge.ts
6165
+ import readline2 from "node:readline";
6268
6166
 
6269
6167
  // src/github/queries.ts
6270
6168
  function buildRepoFilter(repos) {
@@ -6378,6 +6276,272 @@ async function enrichAllWithReviews(prs, user) {
6378
6276
  return results;
6379
6277
  }
6380
6278
 
6279
+ // src/identifier.ts
6280
+ var URL_RE = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/;
6281
+ var REPO_NUMBER_RE = /^([^/]+\/[^#]+)#(\d+)$/;
6282
+ var NUMBER_RE = /^\d+$/;
6283
+ function parseIdentifier(input) {
6284
+ const urlMatch = input.match(URL_RE);
6285
+ if (urlMatch) {
6286
+ return {
6287
+ kind: "url",
6288
+ owner: urlMatch[1],
6289
+ repo: urlMatch[2],
6290
+ number: Number.parseInt(urlMatch[3], 10)
6291
+ };
6292
+ }
6293
+ const repoMatch = input.match(REPO_NUMBER_RE);
6294
+ if (repoMatch) {
6295
+ const [owner, repo] = repoMatch[1].split("/");
6296
+ return {
6297
+ kind: "repo-number",
6298
+ owner,
6299
+ repo,
6300
+ number: Number.parseInt(repoMatch[2], 10)
6301
+ };
6302
+ }
6303
+ if (NUMBER_RE.test(input)) {
6304
+ return { kind: "number-only", number: Number.parseInt(input, 10) };
6305
+ }
6306
+ throw new Error(`Invalid PR identifier: "${input}"
6307
+ Examples: 482, org/repo#482, https://github.com/org/repo/pull/482`);
6308
+ }
6309
+ async function resolveIdentifier(input, config) {
6310
+ const parsed = parseIdentifier(input);
6311
+ if (parsed.kind === "url" || parsed.kind === "repo-number") {
6312
+ return {
6313
+ owner: parsed.owner,
6314
+ repo: parsed.repo,
6315
+ number: parsed.number,
6316
+ url: `https://github.com/${parsed.owner}/${parsed.repo}/pull/${parsed.number}`,
6317
+ title: "",
6318
+ author: "",
6319
+ updatedAt: ""
6320
+ };
6321
+ }
6322
+ return findPRByNumber(parsed.number, config);
6323
+ }
6324
+ async function findPRByNumber(prNumber, config) {
6325
+ const user = config.user ?? await getAuthenticatedUser();
6326
+ process.stderr.write(`Searching for PR #${prNumber}...
6327
+ `);
6328
+ const [reviewed, requested, authored] = await Promise.all([
6329
+ fetchReviewedPRs(user, config.repos),
6330
+ fetchRequestedPRs(user, config.repos),
6331
+ fetchAuthoredPRs(user, config.repos)
6332
+ ]);
6333
+ const seen = new Set;
6334
+ const allPRs = [];
6335
+ for (const pr2 of [...reviewed, ...requested, ...authored]) {
6336
+ const key = `${pr2.repo}#${pr2.number}`;
6337
+ if (!seen.has(key)) {
6338
+ seen.add(key);
6339
+ allPRs.push(pr2);
6340
+ }
6341
+ }
6342
+ const matches = allPRs.filter((pr2) => pr2.number === prNumber);
6343
+ if (matches.length === 0) {
6344
+ throw new Error(`No PR #${prNumber} found in your queue. Use the full format: org/repo#${prNumber}`);
6345
+ }
6346
+ if (matches.length > 1) {
6347
+ const options = matches.map((pr2) => ` ${pr2.repo}#${pr2.number}`).join(`
6348
+ `);
6349
+ throw new Error(`Multiple PRs found with #${prNumber}:
6350
+ ${options}
6351
+ Specify which one: prq open org/repo#${prNumber}`);
6352
+ }
6353
+ const pr = matches[0];
6354
+ const [owner, repo] = pr.repo.split("/");
6355
+ return {
6356
+ owner,
6357
+ repo,
6358
+ number: pr.number,
6359
+ url: pr.url,
6360
+ title: pr.title,
6361
+ author: pr.author,
6362
+ updatedAt: pr.updatedAt
6363
+ };
6364
+ }
6365
+
6366
+ // src/commands/nudge.ts
6367
+ function ask2(rl, question) {
6368
+ return new Promise((resolve) => rl.question(question, resolve));
6369
+ }
6370
+ function daysAgo(dateStr) {
6371
+ return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
6372
+ }
6373
+ async function nudgeCommand(identifier, config, options) {
6374
+ const pr = await resolveIdentifier(identifier, config);
6375
+ const client = getClient();
6376
+ const { data } = await client.pulls.get({
6377
+ owner: pr.owner,
6378
+ repo: pr.repo,
6379
+ pull_number: pr.number
6380
+ });
6381
+ const days = daysAgo(data.updated_at);
6382
+ const message = options.message ?? `Hey @${data.user?.login ?? pr.author}, is this PR still active? It's been ${days} day${days === 1 ? "" : "s"} since the last activity.`;
6383
+ const label = `${pr.owner}/${pr.repo}#${pr.number}`;
6384
+ if (!options.yes) {
6385
+ console.log();
6386
+ console.log(source_default.bold(` ${data.title}`));
6387
+ console.log(source_default.dim(` ${label}`));
6388
+ console.log();
6389
+ console.log(` ${source_default.dim("Message:")} ${message}`);
6390
+ console.log();
6391
+ const rl = readline2.createInterface({
6392
+ input: process.stdin,
6393
+ output: process.stdout
6394
+ });
6395
+ try {
6396
+ const answer = await ask2(rl, source_default.yellow(" Post this comment? (y/n) "));
6397
+ if (answer.toLowerCase() !== "y") {
6398
+ console.log(source_default.dim(" Cancelled."));
6399
+ return;
6400
+ }
6401
+ } finally {
6402
+ rl.close();
6403
+ }
6404
+ }
6405
+ await client.issues.createComment({
6406
+ owner: pr.owner,
6407
+ repo: pr.repo,
6408
+ issue_number: pr.number,
6409
+ body: message
6410
+ });
6411
+ console.log(source_default.green(` Comment posted on ${label}`));
6412
+ }
6413
+
6414
+ // src/platform.ts
6415
+ import { spawn } from "node:child_process";
6416
+ function openUrl(url) {
6417
+ return new Promise((resolve, reject) => {
6418
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : process.platform === "win32" ? "cmd" : null;
6419
+ if (!cmd) {
6420
+ reject(new Error(`Unsupported platform: ${process.platform}`));
6421
+ return;
6422
+ }
6423
+ const args = process.platform === "win32" ? ["/c", "start", url] : [url];
6424
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
6425
+ child.unref();
6426
+ child.on("error", reject);
6427
+ child.on("close", () => resolve());
6428
+ });
6429
+ }
6430
+
6431
+ // src/commands/open.ts
6432
+ async function openCommand(identifier, config) {
6433
+ const pr = await resolveIdentifier(identifier, config);
6434
+ const label = `${pr.owner}/${pr.repo}#${pr.number}`;
6435
+ process.stderr.write(source_default.dim(`Opening ${label}...
6436
+ `));
6437
+ await openUrl(pr.url);
6438
+ }
6439
+
6440
+ // src/categorize.ts
6441
+ function timeAgo(dateStr) {
6442
+ const now = Date.now();
6443
+ const then = new Date(dateStr).getTime();
6444
+ const diffMs = now - then;
6445
+ const diffMins = Math.floor(diffMs / 60000);
6446
+ const diffHours = Math.floor(diffMs / 3600000);
6447
+ const diffDays = Math.floor(diffMs / 86400000);
6448
+ if (diffMins < 60)
6449
+ return `${diffMins}m ago`;
6450
+ if (diffHours < 24)
6451
+ return `${diffHours}h ago`;
6452
+ return `${diffDays}d ago`;
6453
+ }
6454
+ function daysAgo2(dateStr) {
6455
+ return Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
6456
+ }
6457
+ function categorize(reviewedPRs, requestedPRs, authoredPRs, staleDays) {
6458
+ const results = [];
6459
+ const seen = new Set;
6460
+ const key = (pr) => `${pr.repo}#${pr.number}`;
6461
+ const requestedKeys = new Set(requestedPRs.map(key));
6462
+ for (const pr of reviewedPRs) {
6463
+ const k = key(pr);
6464
+ if (seen.has(k))
6465
+ continue;
6466
+ const reviewDate = new Date(pr.userLastReviewedAt).getTime();
6467
+ const commitDate = new Date(pr.latestCommitAt).getTime();
6468
+ if (commitDate > reviewDate) {
6469
+ seen.add(k);
6470
+ results.push({
6471
+ category: "needs-re-review",
6472
+ repo: pr.repo,
6473
+ number: pr.number,
6474
+ title: pr.title,
6475
+ author: pr.author,
6476
+ url: pr.url,
6477
+ isDraft: pr.isDraft,
6478
+ updatedAt: pr.updatedAt,
6479
+ detail: `New commits since your review ${timeAgo(pr.userLastReviewedAt)}`
6480
+ });
6481
+ }
6482
+ }
6483
+ for (const pr of requestedPRs) {
6484
+ const k = key(pr);
6485
+ if (seen.has(k))
6486
+ continue;
6487
+ seen.add(k);
6488
+ results.push({
6489
+ category: "requested",
6490
+ repo: pr.repo,
6491
+ number: pr.number,
6492
+ title: pr.title,
6493
+ author: pr.author,
6494
+ url: pr.url,
6495
+ isDraft: pr.isDraft,
6496
+ updatedAt: pr.updatedAt,
6497
+ detail: `Requested ${timeAgo(pr.updatedAt)}`
6498
+ });
6499
+ }
6500
+ for (const pr of reviewedPRs) {
6501
+ const k = key(pr);
6502
+ if (seen.has(k))
6503
+ continue;
6504
+ if (requestedKeys.has(k))
6505
+ continue;
6506
+ const inactive = daysAgo2(pr.updatedAt);
6507
+ if (inactive >= staleDays) {
6508
+ seen.add(k);
6509
+ results.push({
6510
+ category: "stale",
6511
+ repo: pr.repo,
6512
+ number: pr.number,
6513
+ title: pr.title,
6514
+ author: pr.author,
6515
+ url: pr.url,
6516
+ isDraft: pr.isDraft,
6517
+ updatedAt: pr.updatedAt,
6518
+ detail: `No activity for ${inactive} days`
6519
+ });
6520
+ }
6521
+ }
6522
+ for (const pr of authoredPRs) {
6523
+ const k = key(pr);
6524
+ if (seen.has(k))
6525
+ continue;
6526
+ if (pr.requestedReviewers.length > 0) {
6527
+ seen.add(k);
6528
+ const reviewers = pr.requestedReviewers.map((r) => `@${r}`).join(", ");
6529
+ results.push({
6530
+ category: "waiting-on-others",
6531
+ repo: pr.repo,
6532
+ number: pr.number,
6533
+ title: pr.title,
6534
+ author: pr.author,
6535
+ url: pr.url,
6536
+ isDraft: pr.isDraft,
6537
+ updatedAt: pr.updatedAt,
6538
+ detail: `Waiting on review from ${reviewers}`
6539
+ });
6540
+ }
6541
+ }
6542
+ return results;
6543
+ }
6544
+
6381
6545
  // src/output.ts
6382
6546
  var CATEGORY_CONFIG = {
6383
6547
  "needs-re-review": {
@@ -6481,7 +6645,7 @@ async function statusCommand(config, json) {
6481
6645
  import fs2 from "node:fs";
6482
6646
  import path2 from "node:path";
6483
6647
 
6484
- // node_modules/zod/v3/external.js
6648
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
6485
6649
  var exports_external = {};
6486
6650
  __export(exports_external, {
6487
6651
  void: () => voidType,
@@ -6593,7 +6757,7 @@ __export(exports_external, {
6593
6757
  BRAND: () => BRAND
6594
6758
  });
6595
6759
 
6596
- // node_modules/zod/v3/helpers/util.js
6760
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
6597
6761
  var util;
6598
6762
  (function(util2) {
6599
6763
  util2.assertEqual = (_) => {};
@@ -6724,7 +6888,7 @@ var getParsedType = (data) => {
6724
6888
  }
6725
6889
  };
6726
6890
 
6727
- // node_modules/zod/v3/ZodError.js
6891
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
6728
6892
  var ZodIssueCode = util.arrayToEnum([
6729
6893
  "invalid_type",
6730
6894
  "invalid_literal",
@@ -6843,7 +7007,7 @@ ZodError.create = (issues) => {
6843
7007
  return error;
6844
7008
  };
6845
7009
 
6846
- // node_modules/zod/v3/locales/en.js
7010
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
6847
7011
  var errorMap = (issue, _ctx) => {
6848
7012
  let message;
6849
7013
  switch (issue.code) {
@@ -6946,7 +7110,7 @@ var errorMap = (issue, _ctx) => {
6946
7110
  };
6947
7111
  var en_default = errorMap;
6948
7112
 
6949
- // node_modules/zod/v3/errors.js
7113
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
6950
7114
  var overrideErrorMap = en_default;
6951
7115
  function setErrorMap(map) {
6952
7116
  overrideErrorMap = map;
@@ -6954,7 +7118,7 @@ function setErrorMap(map) {
6954
7118
  function getErrorMap() {
6955
7119
  return overrideErrorMap;
6956
7120
  }
6957
- // node_modules/zod/v3/helpers/parseUtil.js
7121
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
6958
7122
  var makeIssue = (params) => {
6959
7123
  const { data, path: path2, errorMaps, issueData } = params;
6960
7124
  const fullPath = [...path2, ...issueData.path || []];
@@ -7060,14 +7224,14 @@ var isAborted = (x) => x.status === "aborted";
7060
7224
  var isDirty = (x) => x.status === "dirty";
7061
7225
  var isValid = (x) => x.status === "valid";
7062
7226
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
7063
- // node_modules/zod/v3/helpers/errorUtil.js
7227
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
7064
7228
  var errorUtil;
7065
7229
  (function(errorUtil2) {
7066
7230
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
7067
7231
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
7068
7232
  })(errorUtil || (errorUtil = {}));
7069
7233
 
7070
- // node_modules/zod/v3/types.js
7234
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
7071
7235
  class ParseInputLazyPath {
7072
7236
  constructor(parent, value, path2, key) {
7073
7237
  this._cachedPath = [];
@@ -10482,9 +10646,13 @@ function loadConfig(cliOverrides) {
10482
10646
  // src/cli.ts
10483
10647
  function getVersion() {
10484
10648
  const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
10485
- const pkgPath = path3.resolve(__dirname2, "../../package.json");
10486
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
10487
- return pkg.version;
10649
+ for (const rel of ["../../package.json", "../package.json"]) {
10650
+ const p = path3.resolve(__dirname2, rel);
10651
+ if (fs3.existsSync(p)) {
10652
+ return JSON.parse(fs3.readFileSync(p, "utf8")).version;
10653
+ }
10654
+ }
10655
+ return "0.0.0";
10488
10656
  }
10489
10657
  function createCLI() {
10490
10658
  const program2 = new Command;
@@ -10496,6 +10664,17 @@ function createCLI() {
10496
10664
  });
10497
10665
  await statusCommand(config, opts.json ?? false);
10498
10666
  });
10667
+ program2.command("open <identifier>").description("Open a PR in the browser").action(async (identifier) => {
10668
+ const config = loadConfig({});
10669
+ await openCommand(identifier, config);
10670
+ });
10671
+ program2.command("nudge <identifier>").description("Post a nudge comment on a PR").option("-m, --message <msg>", "Custom nudge message").option("-y, --yes", "Skip confirmation").action(async (identifier, opts) => {
10672
+ const config = loadConfig({});
10673
+ await nudgeCommand(identifier, config, {
10674
+ message: opts.message,
10675
+ yes: opts.yes ?? false
10676
+ });
10677
+ });
10499
10678
  program2.command("init").description("Create config file interactively").action(async () => {
10500
10679
  await initCommand();
10501
10680
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prq-cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "PR Queue — see what code reviews need your attention",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,12 +10,10 @@
10
10
  "dist"
11
11
  ],
12
12
  "scripts": {
13
- "build": "bun build src/bin/prq.ts --outdir dist/bin --target node",
14
13
  "dev": "bun run src/bin/prq.ts",
14
+ "build": "bun build src/bin/prq.ts --outdir dist/bin --target node",
15
15
  "typecheck": "tsc --noEmit",
16
- "test": "bun test",
17
- "lint": "biome check .",
18
- "lint:fix": "biome check --write ."
16
+ "test": "bun test"
19
17
  },
20
18
  "dependencies": {
21
19
  "@octokit/rest": "^21.1.1",
@@ -23,17 +21,6 @@
23
21
  "chalk": "^5.4.1",
24
22
  "zod": "^3.24.4"
25
23
  },
26
- "devDependencies": {
27
- "@biomejs/biome": "^2.4.8",
28
- "@semantic-release/commit-analyzer": "^13.0.1",
29
- "@semantic-release/git": "^10.0.1",
30
- "@semantic-release/github": "^12.0.6",
31
- "@semantic-release/npm": "^13.1.5",
32
- "@types/node": "^22.0.0",
33
- "bun-types": "^1.3.11",
34
- "semantic-release": "^25.0.3",
35
- "typescript": "^5.7.0"
36
- },
37
24
  "engines": {
38
25
  "node": ">=20"
39
26
  },
@@ -48,7 +35,8 @@
48
35
  "author": "Caio Pizzol",
49
36
  "repository": {
50
37
  "type": "git",
51
- "url": "https://github.com/caiopizzol/prq.git"
38
+ "url": "git+https://github.com/caiopizzol/prq.git"
52
39
  },
53
- "homepage": "https://github.com/caiopizzol/prq"
40
+ "homepage": "https://github.com/caiopizzol/prq",
41
+ "bugs": "https://github.com/caiopizzol/prq/issues"
54
42
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Caio Pizzol
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.