@oss-autopilot/core 0.41.0 → 0.42.1

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 (63) hide show
  1. package/dist/cli.bundle.cjs +1552 -1318
  2. package/dist/cli.js +593 -69
  3. package/dist/commands/check-integration.d.ts +3 -3
  4. package/dist/commands/check-integration.js +10 -43
  5. package/dist/commands/comments.d.ts +6 -9
  6. package/dist/commands/comments.js +102 -252
  7. package/dist/commands/config.d.ts +8 -2
  8. package/dist/commands/config.js +6 -28
  9. package/dist/commands/daily.d.ts +28 -4
  10. package/dist/commands/daily.js +33 -45
  11. package/dist/commands/dashboard-data.js +7 -6
  12. package/dist/commands/dashboard-server.d.ts +14 -0
  13. package/dist/commands/dashboard-server.js +362 -0
  14. package/dist/commands/dashboard.d.ts +5 -0
  15. package/dist/commands/dashboard.js +51 -1
  16. package/dist/commands/dismiss.d.ts +13 -5
  17. package/dist/commands/dismiss.js +4 -24
  18. package/dist/commands/index.d.ts +33 -0
  19. package/dist/commands/index.js +22 -0
  20. package/dist/commands/init.d.ts +5 -4
  21. package/dist/commands/init.js +4 -14
  22. package/dist/commands/local-repos.d.ts +4 -5
  23. package/dist/commands/local-repos.js +6 -33
  24. package/dist/commands/parse-list.d.ts +3 -4
  25. package/dist/commands/parse-list.js +8 -39
  26. package/dist/commands/read.d.ts +11 -5
  27. package/dist/commands/read.js +4 -18
  28. package/dist/commands/search.d.ts +3 -3
  29. package/dist/commands/search.js +39 -65
  30. package/dist/commands/setup.d.ts +34 -5
  31. package/dist/commands/setup.js +75 -166
  32. package/dist/commands/shelve.d.ts +13 -5
  33. package/dist/commands/shelve.js +4 -24
  34. package/dist/commands/snooze.d.ts +15 -9
  35. package/dist/commands/snooze.js +16 -59
  36. package/dist/commands/startup.d.ts +11 -6
  37. package/dist/commands/startup.js +44 -82
  38. package/dist/commands/status.d.ts +3 -3
  39. package/dist/commands/status.js +10 -29
  40. package/dist/commands/track.d.ts +10 -9
  41. package/dist/commands/track.js +17 -39
  42. package/dist/commands/validation.d.ts +2 -2
  43. package/dist/commands/validation.js +7 -15
  44. package/dist/commands/vet.d.ts +3 -3
  45. package/dist/commands/vet.js +16 -26
  46. package/dist/core/errors.d.ts +9 -0
  47. package/dist/core/errors.js +17 -0
  48. package/dist/core/github-stats.d.ts +14 -21
  49. package/dist/core/github-stats.js +84 -138
  50. package/dist/core/http-cache.d.ts +6 -0
  51. package/dist/core/http-cache.js +16 -4
  52. package/dist/core/index.d.ts +2 -1
  53. package/dist/core/index.js +2 -1
  54. package/dist/core/issue-conversation.js +4 -4
  55. package/dist/core/issue-discovery.js +14 -14
  56. package/dist/core/issue-vetting.js +17 -17
  57. package/dist/core/pr-monitor.d.ts +6 -20
  58. package/dist/core/pr-monitor.js +11 -52
  59. package/dist/core/state.js +4 -5
  60. package/dist/core/utils.d.ts +11 -0
  61. package/dist/core/utils.js +21 -0
  62. package/dist/formatters/json.d.ts +58 -0
  63. package/package.json +5 -1
@@ -1196,8 +1196,8 @@ var require_command = __commonJS({
1196
1196
  "../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/command.js"(exports2) {
1197
1197
  var EventEmitter = require("node:events").EventEmitter;
1198
1198
  var childProcess = require("node:child_process");
1199
- var path9 = require("node:path");
1200
- var fs9 = require("node:fs");
1199
+ var path10 = require("node:path");
1200
+ var fs10 = require("node:fs");
1201
1201
  var process2 = require("node:process");
1202
1202
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1203
1203
  var { CommanderError: CommanderError2 } = require_error();
@@ -2191,7 +2191,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2191
2191
  * @param {string} subcommandName
2192
2192
  */
2193
2193
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2194
- if (fs9.existsSync(executableFile)) return;
2194
+ if (fs10.existsSync(executableFile)) return;
2195
2195
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2196
2196
  const executableMissing = `'${executableFile}' does not exist
2197
2197
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2209,11 +2209,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2209
2209
  let launchWithNode = false;
2210
2210
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2211
2211
  function findFile(baseDir, baseName) {
2212
- const localBin = path9.resolve(baseDir, baseName);
2213
- if (fs9.existsSync(localBin)) return localBin;
2214
- if (sourceExt.includes(path9.extname(baseName))) return void 0;
2212
+ const localBin = path10.resolve(baseDir, baseName);
2213
+ if (fs10.existsSync(localBin)) return localBin;
2214
+ if (sourceExt.includes(path10.extname(baseName))) return void 0;
2215
2215
  const foundExt = sourceExt.find(
2216
- (ext) => fs9.existsSync(`${localBin}${ext}`)
2216
+ (ext) => fs10.existsSync(`${localBin}${ext}`)
2217
2217
  );
2218
2218
  if (foundExt) return `${localBin}${foundExt}`;
2219
2219
  return void 0;
@@ -2225,21 +2225,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2225
2225
  if (this._scriptPath) {
2226
2226
  let resolvedScriptPath;
2227
2227
  try {
2228
- resolvedScriptPath = fs9.realpathSync(this._scriptPath);
2228
+ resolvedScriptPath = fs10.realpathSync(this._scriptPath);
2229
2229
  } catch {
2230
2230
  resolvedScriptPath = this._scriptPath;
2231
2231
  }
2232
- executableDir = path9.resolve(
2233
- path9.dirname(resolvedScriptPath),
2232
+ executableDir = path10.resolve(
2233
+ path10.dirname(resolvedScriptPath),
2234
2234
  executableDir
2235
2235
  );
2236
2236
  }
2237
2237
  if (executableDir) {
2238
2238
  let localFile = findFile(executableDir, executableFile);
2239
2239
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2240
- const legacyName = path9.basename(
2240
+ const legacyName = path10.basename(
2241
2241
  this._scriptPath,
2242
- path9.extname(this._scriptPath)
2242
+ path10.extname(this._scriptPath)
2243
2243
  );
2244
2244
  if (legacyName !== this._name) {
2245
2245
  localFile = findFile(
@@ -2250,7 +2250,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2250
2250
  }
2251
2251
  executableFile = localFile || executableFile;
2252
2252
  }
2253
- launchWithNode = sourceExt.includes(path9.extname(executableFile));
2253
+ launchWithNode = sourceExt.includes(path10.extname(executableFile));
2254
2254
  let proc;
2255
2255
  if (process2.platform !== "win32") {
2256
2256
  if (launchWithNode) {
@@ -3165,7 +3165,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3165
3165
  * @return {Command}
3166
3166
  */
3167
3167
  nameFromFilename(filename) {
3168
- this._name = path9.basename(filename, path9.extname(filename));
3168
+ this._name = path10.basename(filename, path10.extname(filename));
3169
3169
  return this;
3170
3170
  }
3171
3171
  /**
@@ -3179,9 +3179,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3179
3179
  * @param {string} [path]
3180
3180
  * @return {(string|null|Command)}
3181
3181
  */
3182
- executableDir(path10) {
3183
- if (path10 === void 0) return this._executableDir;
3184
- this._executableDir = path10;
3182
+ executableDir(path11) {
3183
+ if (path11 === void 0) return this._executableDir;
3184
+ this._executableDir = path11;
3185
3185
  return this;
3186
3186
  }
3187
3187
  /**
@@ -3498,6 +3498,16 @@ var init_types = __esm({
3498
3498
  });
3499
3499
 
3500
3500
  // src/core/errors.ts
3501
+ function errorMessage(e) {
3502
+ return e instanceof Error ? e.message : String(e);
3503
+ }
3504
+ function getHttpStatusCode(error) {
3505
+ if (error && typeof error === "object" && "status" in error) {
3506
+ const status = error.status;
3507
+ return typeof status === "number" && Number.isFinite(status) ? status : void 0;
3508
+ }
3509
+ return void 0;
3510
+ }
3501
3511
  var OssAutopilotError, ConfigurationError, ValidationError;
3502
3512
  var init_errors = __esm({
3503
3513
  "src/core/errors.ts"() {
@@ -3640,6 +3650,17 @@ function splitRepo(repoFullName) {
3640
3650
  const [owner, repo] = repoFullName.split("/");
3641
3651
  return { owner, repo };
3642
3652
  }
3653
+ function isOwnRepo(owner, username) {
3654
+ return owner.toLowerCase() === username.toLowerCase();
3655
+ }
3656
+ function getCLIVersion() {
3657
+ try {
3658
+ const pkgPath = path.join(path.dirname(process.argv[1]), "..", "package.json");
3659
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
3660
+ } catch {
3661
+ return "0.0.0";
3662
+ }
3663
+ }
3643
3664
  function formatRelativeTime(dateStr) {
3644
3665
  const date = new Date(dateStr);
3645
3666
  const diffMs = Date.now() - date.getTime();
@@ -3681,6 +3702,15 @@ function getGitHubToken() {
3681
3702
  }
3682
3703
  return null;
3683
3704
  }
3705
+ function requireGitHubToken() {
3706
+ const token = getGitHubToken();
3707
+ if (!token) {
3708
+ throw new ConfigurationError(
3709
+ "GitHub authentication required.\n\nOptions:\n 1. Use gh CLI: gh auth login\n 2. Set GITHUB_TOKEN environment variable\n\nThe gh CLI is recommended - install from https://cli.github.com"
3710
+ );
3711
+ }
3712
+ return token;
3713
+ }
3684
3714
  async function getGitHubTokenAsync() {
3685
3715
  if (cachedGitHubToken) {
3686
3716
  return cachedGitHubToken;
@@ -3694,12 +3724,12 @@ async function getGitHubTokenAsync() {
3694
3724
  return cachedGitHubToken;
3695
3725
  }
3696
3726
  try {
3697
- const token = await new Promise((resolve3, reject) => {
3727
+ const token = await new Promise((resolve5, reject) => {
3698
3728
  (0, import_child_process.execFile)("gh", ["auth", "token"], { encoding: "utf-8", timeout: 2e3 }, (error, stdout) => {
3699
3729
  if (error) {
3700
3730
  reject(error);
3701
3731
  } else {
3702
- resolve3(stdout.trim());
3732
+ resolve5(stdout.trim());
3703
3733
  }
3704
3734
  });
3705
3735
  });
@@ -3951,8 +3981,7 @@ var init_state = __esm({
3951
3981
  debug(MODULE2, "Migration complete!");
3952
3982
  return true;
3953
3983
  } catch (error) {
3954
- const errorMessage = error instanceof Error ? error.message : String(error);
3955
- warn(MODULE2, `Failed to migrate state: ${errorMessage}`);
3984
+ warn(MODULE2, `Failed to migrate state: ${errorMessage(error)}`);
3956
3985
  const newStatePath2 = getStatePath();
3957
3986
  if (fs2.existsSync(newStatePath2) && fs2.existsSync(LEGACY_STATE_FILE)) {
3958
3987
  try {
@@ -4104,11 +4133,11 @@ var init_state = __esm({
4104
4133
  try {
4105
4134
  fs2.unlinkSync(path2.join(backupDir, file));
4106
4135
  } catch (error) {
4107
- warn(MODULE2, `Could not delete old backup ${file}:`, error instanceof Error ? error.message : error);
4136
+ warn(MODULE2, `Could not delete old backup ${file}:`, errorMessage(error));
4108
4137
  }
4109
4138
  }
4110
4139
  } catch (error) {
4111
- warn(MODULE2, "Could not clean up backups:", error instanceof Error ? error.message : error);
4140
+ warn(MODULE2, "Could not clean up backups:", errorMessage(error));
4112
4141
  }
4113
4142
  }
4114
4143
  /**
@@ -5891,17 +5920,17 @@ function requestLog(octokit) {
5891
5920
  octokit.log.debug("request", options);
5892
5921
  const start = Date.now();
5893
5922
  const requestOptions = octokit.request.endpoint.parse(options);
5894
- const path9 = requestOptions.url.replace(options.baseUrl, "");
5923
+ const path10 = requestOptions.url.replace(options.baseUrl, "");
5895
5924
  return request2(options).then((response) => {
5896
5925
  const requestId = response.headers["x-github-request-id"];
5897
5926
  octokit.log.info(
5898
- `${requestOptions.method} ${path9} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
5927
+ `${requestOptions.method} ${path10} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
5899
5928
  );
5900
5929
  return response;
5901
5930
  }).catch((error) => {
5902
5931
  const requestId = error.response?.headers["x-github-request-id"] || "UNKNOWN";
5903
5932
  octokit.log.error(
5904
- `${requestOptions.method} ${path9} - ${error.status} with id ${requestId} in ${Date.now() - start}ms`
5933
+ `${requestOptions.method} ${path10} - ${error.status} with id ${requestId} in ${Date.now() - start}ms`
5905
5934
  );
5906
5935
  throw error;
5907
5936
  });
@@ -8970,8 +8999,8 @@ var require_light = __commonJS({
8970
8999
  return this.Promise.resolve();
8971
9000
  }
8972
9001
  yieldLoop(t = 0) {
8973
- return new this.Promise(function(resolve3, reject) {
8974
- return setTimeout(resolve3, t);
9002
+ return new this.Promise(function(resolve5, reject) {
9003
+ return setTimeout(resolve5, t);
8975
9004
  });
8976
9005
  }
8977
9006
  computePenalty() {
@@ -9182,15 +9211,15 @@ var require_light = __commonJS({
9182
9211
  return this._queue.length === 0;
9183
9212
  }
9184
9213
  async _tryToRun() {
9185
- var args, cb, error, reject, resolve3, returned, task;
9214
+ var args, cb, error, reject, resolve5, returned, task;
9186
9215
  if (this._running < 1 && this._queue.length > 0) {
9187
9216
  this._running++;
9188
- ({ task, args, resolve: resolve3, reject } = this._queue.shift());
9217
+ ({ task, args, resolve: resolve5, reject } = this._queue.shift());
9189
9218
  cb = await (async function() {
9190
9219
  try {
9191
9220
  returned = await task(...args);
9192
9221
  return function() {
9193
- return resolve3(returned);
9222
+ return resolve5(returned);
9194
9223
  };
9195
9224
  } catch (error1) {
9196
9225
  error = error1;
@@ -9205,13 +9234,13 @@ var require_light = __commonJS({
9205
9234
  }
9206
9235
  }
9207
9236
  schedule(task, ...args) {
9208
- var promise, reject, resolve3;
9209
- resolve3 = reject = null;
9237
+ var promise, reject, resolve5;
9238
+ resolve5 = reject = null;
9210
9239
  promise = new this.Promise(function(_resolve, _reject) {
9211
- resolve3 = _resolve;
9240
+ resolve5 = _resolve;
9212
9241
  return reject = _reject;
9213
9242
  });
9214
- this._queue.push({ task, args, resolve: resolve3, reject });
9243
+ this._queue.push({ task, args, resolve: resolve5, reject });
9215
9244
  this._tryToRun();
9216
9245
  return promise;
9217
9246
  }
@@ -9612,14 +9641,14 @@ var require_light = __commonJS({
9612
9641
  counts = this._states.counts;
9613
9642
  return counts[0] + counts[1] + counts[2] + counts[3] === at;
9614
9643
  };
9615
- return new this.Promise((resolve3, reject) => {
9644
+ return new this.Promise((resolve5, reject) => {
9616
9645
  if (finished()) {
9617
- return resolve3();
9646
+ return resolve5();
9618
9647
  } else {
9619
9648
  return this.on("done", () => {
9620
9649
  if (finished()) {
9621
9650
  this.removeAllListeners("done");
9622
- return resolve3();
9651
+ return resolve5();
9623
9652
  }
9624
9653
  });
9625
9654
  }
@@ -9712,9 +9741,9 @@ var require_light = __commonJS({
9712
9741
  options = parser$5.load(options, this.jobDefaults);
9713
9742
  }
9714
9743
  task = (...args2) => {
9715
- return new this.Promise(function(resolve3, reject) {
9744
+ return new this.Promise(function(resolve5, reject) {
9716
9745
  return fn(...args2, function(...args3) {
9717
- return (args3[0] != null ? reject : resolve3)(args3);
9746
+ return (args3[0] != null ? reject : resolve5)(args3);
9718
9747
  });
9719
9748
  });
9720
9749
  };
@@ -9882,7 +9911,7 @@ function isAuthRequest(method, pathname) {
9882
9911
  }
9883
9912
  function routeMatcher(paths) {
9884
9913
  const regexes = paths.map(
9885
- (path9) => path9.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
9914
+ (path10) => path10.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
9886
9915
  );
9887
9916
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
9888
9917
  return new RegExp(regex2, "i");
@@ -10210,10 +10239,7 @@ async function cachedRequest(cache, url, fetcher) {
10210
10239
  }
10211
10240
  }
10212
10241
  function isNotModifiedError(err) {
10213
- if (err && typeof err === "object" && "status" in err) {
10214
- return err.status === 304;
10215
- }
10216
- return false;
10242
+ return getHttpStatusCode(err) === 304;
10217
10243
  }
10218
10244
  var fs3, path3, crypto, MODULE4, DEFAULT_MAX_AGE_MS, HttpCache, _httpCache;
10219
10245
  var init_http_cache = __esm({
@@ -10224,6 +10250,7 @@ var init_http_cache = __esm({
10224
10250
  crypto = __toESM(require("crypto"), 1);
10225
10251
  init_utils();
10226
10252
  init_logger();
10253
+ init_errors();
10227
10254
  MODULE4 = "http-cache";
10228
10255
  DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
10229
10256
  HttpCache = class {
@@ -10241,6 +10268,18 @@ var init_http_cache = __esm({
10241
10268
  pathFor(url) {
10242
10269
  return path3.join(this.cacheDir, `${this.keyFor(url)}.json`);
10243
10270
  }
10271
+ /**
10272
+ * Return the cached body if the entry exists and is younger than `maxAgeMs`.
10273
+ * Useful for time-based caching where ETag validation isn't applicable
10274
+ * (e.g., caching aggregated results from paginated API calls).
10275
+ */
10276
+ getIfFresh(key, maxAgeMs) {
10277
+ const entry = this.get(key);
10278
+ if (!entry) return null;
10279
+ const age = Date.now() - new Date(entry.cachedAt).getTime();
10280
+ if (!Number.isFinite(age) || age < 0 || age > maxAgeMs) return null;
10281
+ return entry.body;
10282
+ }
10244
10283
  /**
10245
10284
  * Look up a cached response. Returns `null` if no cache entry exists.
10246
10285
  */
@@ -10804,11 +10843,28 @@ var init_display_utils = __esm({
10804
10843
  });
10805
10844
 
10806
10845
  // src/core/github-stats.ts
10807
- async function fetchUserMergedPRCounts(octokit, githubUsername) {
10846
+ function isCachedPRCounts(v) {
10847
+ if (typeof v !== "object" || v === null) return false;
10848
+ const obj = v;
10849
+ return Array.isArray(obj.reposEntries) && typeof obj.monthlyCounts === "object" && obj.monthlyCounts !== null && typeof obj.monthlyOpenedCounts === "object" && obj.monthlyOpenedCounts !== null && typeof obj.dailyActivityCounts === "object" && obj.dailyActivityCounts !== null;
10850
+ }
10851
+ async function fetchUserPRCounts(octokit, githubUsername, query, label, accumulateRepo) {
10808
10852
  if (!githubUsername) {
10809
10853
  return { repos: /* @__PURE__ */ new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
10810
10854
  }
10811
- debug(MODULE6, `Fetching merged PR counts for @${githubUsername}...`);
10855
+ const cache = getHttpCache();
10856
+ const cacheKey = `pr-counts:${label}:${githubUsername}`;
10857
+ const cached = cache.getIfFresh(cacheKey, PR_COUNTS_CACHE_TTL_MS);
10858
+ if (cached && isCachedPRCounts(cached)) {
10859
+ debug(MODULE6, `Using cached ${label} PR counts for @${githubUsername}`);
10860
+ return {
10861
+ repos: new Map(cached.reposEntries),
10862
+ monthlyCounts: cached.monthlyCounts,
10863
+ monthlyOpenedCounts: cached.monthlyOpenedCounts,
10864
+ dailyActivityCounts: cached.dailyActivityCounts
10865
+ };
10866
+ }
10867
+ debug(MODULE6, `Fetching ${label} PR counts for @${githubUsername}...`);
10812
10868
  const repos = /* @__PURE__ */ new Map();
10813
10869
  const monthlyCounts = {};
10814
10870
  const monthlyOpenedCounts = {};
@@ -10817,7 +10873,7 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
10817
10873
  let fetched = 0;
10818
10874
  while (true) {
10819
10875
  const { data } = await octokit.search.issuesAndPullRequests({
10820
- q: `is:pr is:merged author:${githubUsername}`,
10876
+ q: `is:pr ${query} author:${githubUsername}`,
10821
10877
  sort: "updated",
10822
10878
  order: "desc",
10823
10879
  per_page: 100,
@@ -10826,25 +10882,18 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
10826
10882
  for (const item of data.items) {
10827
10883
  const parsed = extractOwnerRepo(item.html_url);
10828
10884
  if (!parsed) {
10829
- warn(MODULE6, `Skipping merged PR with unparseable URL: ${item.html_url}`);
10885
+ warn(MODULE6, `Skipping ${label} PR with unparseable URL: ${item.html_url}`);
10830
10886
  continue;
10831
10887
  }
10832
10888
  const { owner } = parsed;
10833
10889
  const repo = `${owner}/${parsed.repo}`;
10834
- if (owner.toLowerCase() === githubUsername.toLowerCase()) continue;
10835
- const mergedAt = item.pull_request?.merged_at || item.closed_at || "";
10836
- const existing = repos.get(repo);
10837
- if (existing) {
10838
- existing.count += 1;
10839
- if (mergedAt && mergedAt > existing.lastMergedAt) {
10840
- existing.lastMergedAt = mergedAt;
10841
- }
10842
- } else {
10843
- repos.set(repo, { count: 1, lastMergedAt: mergedAt });
10844
- }
10845
- if (mergedAt) {
10846
- const month = mergedAt.slice(0, 7);
10890
+ if (isOwnRepo(owner, githubUsername)) continue;
10891
+ const primaryDate = accumulateRepo(repos, repo, item);
10892
+ if (primaryDate) {
10893
+ const month = primaryDate.slice(0, 7);
10847
10894
  monthlyCounts[month] = (monthlyCounts[month] || 0) + 1;
10895
+ const day = primaryDate.slice(0, 10);
10896
+ if (day.length === 10) dailyActivityCounts[day] = (dailyActivityCounts[day] || 0) + 1;
10848
10897
  }
10849
10898
  if (item.created_at) {
10850
10899
  const openedMonth = item.created_at.slice(0, 7);
@@ -10852,10 +10901,6 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
10852
10901
  const openedDay = item.created_at.slice(0, 10);
10853
10902
  if (openedDay.length === 10) dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
10854
10903
  }
10855
- if (mergedAt) {
10856
- const mergedDay = mergedAt.slice(0, 10);
10857
- if (mergedDay.length === 10) dailyActivityCounts[mergedDay] = (dailyActivityCounts[mergedDay] || 0) + 1;
10858
- }
10859
10904
  }
10860
10905
  fetched += data.items.length;
10861
10906
  if (fetched >= data.total_count || fetched >= 1e3 || data.items.length === 0) {
@@ -10863,59 +10908,41 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
10863
10908
  }
10864
10909
  page++;
10865
10910
  }
10866
- debug(MODULE6, `Found ${fetched} merged PRs across ${repos.size} repos`);
10911
+ debug(MODULE6, `Found ${fetched} ${label} PRs across ${repos.size} repos`);
10912
+ cache.set(cacheKey, "", {
10913
+ reposEntries: Array.from(repos.entries()),
10914
+ monthlyCounts,
10915
+ monthlyOpenedCounts,
10916
+ dailyActivityCounts
10917
+ });
10867
10918
  return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
10868
10919
  }
10869
- async function fetchUserClosedPRCounts(octokit, githubUsername) {
10870
- if (!githubUsername) {
10871
- return { repos: /* @__PURE__ */ new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
10872
- }
10873
- debug(MODULE6, `Fetching closed PR counts for @${githubUsername}...`);
10874
- const repos = /* @__PURE__ */ new Map();
10875
- const monthlyCounts = {};
10876
- const monthlyOpenedCounts = {};
10877
- const dailyActivityCounts = {};
10878
- let page = 1;
10879
- let fetched = 0;
10880
- while (true) {
10881
- const { data } = await octokit.search.issuesAndPullRequests({
10882
- q: `is:pr is:closed is:unmerged author:${githubUsername}`,
10883
- sort: "updated",
10884
- order: "desc",
10885
- per_page: 100,
10886
- page
10887
- });
10888
- for (const item of data.items) {
10889
- const parsed = extractOwnerRepo(item.html_url);
10890
- if (!parsed) {
10891
- warn(MODULE6, `Skipping closed PR with unparseable URL: ${item.html_url}`);
10892
- continue;
10893
- }
10894
- const { owner } = parsed;
10895
- const repo = `${owner}/${parsed.repo}`;
10896
- if (owner.toLowerCase() === githubUsername.toLowerCase()) continue;
10897
- repos.set(repo, (repos.get(repo) || 0) + 1);
10898
- if (item.closed_at) {
10899
- const closedMonth = item.closed_at.slice(0, 7);
10900
- monthlyCounts[closedMonth] = (monthlyCounts[closedMonth] || 0) + 1;
10901
- const closedDay = item.closed_at.slice(0, 10);
10902
- if (closedDay.length === 10) dailyActivityCounts[closedDay] = (dailyActivityCounts[closedDay] || 0) + 1;
10903
- }
10904
- if (item.created_at) {
10905
- const openedMonth = item.created_at.slice(0, 7);
10906
- monthlyOpenedCounts[openedMonth] = (monthlyOpenedCounts[openedMonth] || 0) + 1;
10907
- const openedDay = item.created_at.slice(0, 10);
10908
- if (openedDay.length === 10) dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
10909
- }
10920
+ function fetchUserMergedPRCounts(octokit, githubUsername) {
10921
+ return fetchUserPRCounts(octokit, githubUsername, "is:merged", "merged", (repos, repo, item) => {
10922
+ if (!item.pull_request?.merged_at) {
10923
+ warn(
10924
+ MODULE6,
10925
+ `merged_at missing for merged PR ${item.html_url}${item.closed_at ? ", falling back to closed_at" : ", no date available"}`
10926
+ );
10910
10927
  }
10911
- fetched += data.items.length;
10912
- if (fetched >= data.total_count || fetched >= 1e3 || data.items.length === 0) {
10913
- break;
10928
+ const mergedAt = item.pull_request?.merged_at || item.closed_at || "";
10929
+ const existing = repos.get(repo);
10930
+ if (existing) {
10931
+ existing.count += 1;
10932
+ if (mergedAt && mergedAt > existing.lastMergedAt) {
10933
+ existing.lastMergedAt = mergedAt;
10934
+ }
10935
+ } else {
10936
+ repos.set(repo, { count: 1, lastMergedAt: mergedAt });
10914
10937
  }
10915
- page++;
10916
- }
10917
- debug(MODULE6, `Found ${fetched} closed (unmerged) PRs across ${repos.size} repos`);
10918
- return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
10938
+ return mergedAt;
10939
+ });
10940
+ }
10941
+ function fetchUserClosedPRCounts(octokit, githubUsername) {
10942
+ return fetchUserPRCounts(octokit, githubUsername, "is:closed is:unmerged", "closed", (repos, repo, item) => {
10943
+ repos.set(repo, (repos.get(repo) || 0) + 1);
10944
+ return item.closed_at || "";
10945
+ });
10919
10946
  }
10920
10947
  async function fetchRecentPRs(octokit, config, query, label, days, mapItem) {
10921
10948
  if (!config.githubUsername) {
@@ -10940,7 +10967,7 @@ async function fetchRecentPRs(octokit, config, query, label, days, mapItem) {
10940
10967
  continue;
10941
10968
  }
10942
10969
  const repo = `${parsed.owner}/${parsed.repo}`;
10943
- if (parsed.owner.toLowerCase() === config.githubUsername.toLowerCase()) continue;
10970
+ if (isOwnRepo(parsed.owner, config.githubUsername)) continue;
10944
10971
  if (config.excludeRepos.includes(repo)) continue;
10945
10972
  if (config.excludeOrgs?.some((org) => parsed.owner.toLowerCase() === org.toLowerCase())) continue;
10946
10973
  results.push(mapItem(item, { owner: parsed.owner, repo, number: parsed.number }));
@@ -10989,14 +11016,15 @@ async function fetchRecentlyMergedPRs(octokit, config, days = 7) {
10989
11016
  }
10990
11017
  );
10991
11018
  }
10992
- var MODULE6;
11019
+ var MODULE6, PR_COUNTS_CACHE_TTL_MS;
10993
11020
  var init_github_stats = __esm({
10994
11021
  "src/core/github-stats.ts"() {
10995
11022
  "use strict";
10996
11023
  init_utils();
10997
- init_errors();
10998
11024
  init_logger();
11025
+ init_http_cache();
10999
11026
  MODULE6 = "github-stats";
11027
+ PR_COUNTS_CACHE_TTL_MS = 60 * 60 * 1e3;
11000
11028
  }
11001
11029
  });
11002
11030
 
@@ -11097,9 +11125,9 @@ var init_pr_monitor = __esm({
11097
11125
  const pr = await this.fetchPRDetails(item.html_url);
11098
11126
  if (pr) prs.push(pr);
11099
11127
  } catch (error) {
11100
- const errorMessage = error instanceof Error ? error.message : String(error);
11101
- warn("pr-monitor", `Error fetching ${item.html_url}: ${errorMessage}`);
11102
- failures.push({ prUrl: item.html_url, error: errorMessage });
11128
+ const errMsg = errorMessage(error);
11129
+ warn("pr-monitor", `Error fetching ${item.html_url}: ${errMsg}`);
11130
+ failures.push({ prUrl: item.html_url, error: errMsg });
11103
11131
  }
11104
11132
  },
11105
11133
  MAX_CONCURRENT_REQUESTS
@@ -11146,12 +11174,12 @@ var init_pr_monitor = __esm({
11146
11174
  paginateAll(
11147
11175
  (page) => this.octokit.pulls.listReviewComments({ owner, repo, pull_number: number, per_page: 100, page })
11148
11176
  ).catch((err) => {
11149
- const status2 = err?.status;
11177
+ const status2 = getHttpStatusCode(err);
11150
11178
  if (status2 === 429) {
11151
11179
  throw err;
11152
11180
  }
11153
11181
  if (status2 === 403) {
11154
- const msg = (err?.message ?? "").toLowerCase();
11182
+ const msg = errorMessage(err).toLowerCase();
11155
11183
  if (msg.includes("rate limit") || msg.includes("abuse detection")) {
11156
11184
  throw err;
11157
11185
  }
@@ -11318,7 +11346,7 @@ var init_pr_monitor = __esm({
11318
11346
  this.octokit.repos.getCombinedStatusForRef({ owner, repo, ref: sha }),
11319
11347
  // 404 is expected for repos without check runs configured; log other errors for debugging
11320
11348
  this.octokit.checks.listForRef({ owner, repo, ref: sha }).catch((err) => {
11321
- const status = err?.status;
11349
+ const status = getHttpStatusCode(err);
11322
11350
  if (status === 404) {
11323
11351
  debug("pr-monitor", `Check runs 404 for ${owner}/${repo}@${sha.slice(0, 7)} (no checks configured)`);
11324
11352
  } else {
@@ -11344,8 +11372,8 @@ var init_pr_monitor = __esm({
11344
11372
  const combinedAnalysis = analyzeCombinedStatus(combinedStatus);
11345
11373
  return mergeStatuses(checkRunAnalysis, combinedAnalysis, checkRuns.length);
11346
11374
  } catch (error) {
11347
- const statusCode = error.status;
11348
- const errorMessage = error instanceof Error ? error.message : String(error);
11375
+ const statusCode = getHttpStatusCode(error);
11376
+ const errMsg = errorMessage(error);
11349
11377
  if (statusCode === 401) {
11350
11378
  warn("pr-monitor", `CI check failed for ${owner}/${repo}: Invalid token`);
11351
11379
  } else if (statusCode === 403) {
@@ -11354,7 +11382,7 @@ var init_pr_monitor = __esm({
11354
11382
  debug("pr-monitor", `CI check 404 for ${owner}/${repo} (no CI configured)`);
11355
11383
  return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
11356
11384
  } else {
11357
- warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errorMessage}`);
11385
+ warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errMsg}`);
11358
11386
  }
11359
11387
  return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
11360
11388
  }
@@ -11415,10 +11443,7 @@ var init_pr_monitor = __esm({
11415
11443
  results.set(result.value.repo, result.value.stars);
11416
11444
  } else {
11417
11445
  chunkFailures++;
11418
- warn(
11419
- MODULE7,
11420
- `Failed to fetch stars for ${chunk[j]}: ${result.reason instanceof Error ? result.reason.message : result.reason}`
11421
- );
11446
+ warn(MODULE7, `Failed to fetch stars for ${chunk[j]}: ${errorMessage(result.reason)}`);
11422
11447
  }
11423
11448
  }
11424
11449
  if (chunkFailures === chunk.length && chunk.length > 0) {
@@ -11432,42 +11457,6 @@ var init_pr_monitor = __esm({
11432
11457
  debug(MODULE7, `Fetched star counts for ${results.size}/${repos.length} repos`);
11433
11458
  return results;
11434
11459
  }
11435
- /**
11436
- * Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
11437
- * Returns parsed search results that pass all filters.
11438
- */
11439
- async fetchRecentPRs(query, label, days, mapItem) {
11440
- const config = this.stateManager.getState().config;
11441
- if (!config.githubUsername) {
11442
- warn(MODULE7, `Skipping recently ${label} PRs fetch: no githubUsername configured. Run /setup-oss to configure.`);
11443
- return [];
11444
- }
11445
- const sinceDate = /* @__PURE__ */ new Date();
11446
- sinceDate.setDate(sinceDate.getDate() - days);
11447
- const since = sinceDate.toISOString().split("T")[0];
11448
- debug(MODULE7, `Fetching recently ${label} PRs for @${config.githubUsername} (since ${since})...`);
11449
- const { data } = await this.octokit.search.issuesAndPullRequests({
11450
- q: query.replace("{username}", config.githubUsername).replace("{since}", since),
11451
- sort: "updated",
11452
- order: "desc",
11453
- per_page: 100
11454
- });
11455
- const results = [];
11456
- for (const item of data.items) {
11457
- const parsed = parseGitHubUrl(item.html_url);
11458
- if (!parsed) {
11459
- warn(MODULE7, `Could not parse GitHub URL from API response: ${item.html_url}`);
11460
- continue;
11461
- }
11462
- const repo = `${parsed.owner}/${parsed.repo}`;
11463
- if (parsed.owner.toLowerCase() === config.githubUsername.toLowerCase()) continue;
11464
- if (config.excludeRepos.includes(repo)) continue;
11465
- if (config.excludeOrgs?.some((org) => parsed.owner.toLowerCase() === org.toLowerCase())) continue;
11466
- results.push(mapItem(item, { owner: parsed.owner, repo, number: parsed.number }));
11467
- }
11468
- debug(MODULE7, `Found ${results.length} recently ${label} PRs`);
11469
- return results;
11470
- }
11471
11460
  /**
11472
11461
  * Fetch PRs closed without merge in the last N days.
11473
11462
  * Delegates to github-stats module.
@@ -11892,7 +11881,7 @@ var init_issue_vetting = __esm({
11892
11881
  if (_IssueVetter.isRateLimitError(error)) {
11893
11882
  rateLimitFailures++;
11894
11883
  }
11895
- warn(MODULE8, `Error vetting issue ${url}:`, error instanceof Error ? error.message : error);
11884
+ warn(MODULE8, `Error vetting issue ${url}:`, errorMessage(error));
11896
11885
  });
11897
11886
  pending.push(task);
11898
11887
  if (pending.length >= MAX_CONCURRENT_REQUESTS2) {
@@ -11912,10 +11901,10 @@ var init_issue_vetting = __esm({
11912
11901
  }
11913
11902
  /** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
11914
11903
  static isRateLimitError(error) {
11915
- const status = error?.status;
11904
+ const status = getHttpStatusCode(error);
11916
11905
  if (status === 429) return true;
11917
11906
  if (status === 403) {
11918
- const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
11907
+ const msg = errorMessage(error).toLowerCase();
11919
11908
  return msg.includes("rate limit");
11920
11909
  }
11921
11910
  return false;
@@ -11941,12 +11930,12 @@ var init_issue_vetting = __esm({
11941
11930
  });
11942
11931
  return { passed: data.total_count === 0 && linkedPRs.length === 0 };
11943
11932
  } catch (error) {
11944
- const errorMessage = error instanceof Error ? error.message : String(error);
11933
+ const errMsg = errorMessage(error);
11945
11934
  warn(
11946
11935
  MODULE8,
11947
- `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errorMessage}. Assuming no existing PR.`
11936
+ `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming no existing PR.`
11948
11937
  );
11949
- return { passed: true, inconclusive: true, reason: errorMessage };
11938
+ return { passed: true, inconclusive: true, reason: errMsg };
11950
11939
  }
11951
11940
  }
11952
11941
  /**
@@ -11962,8 +11951,8 @@ var init_issue_vetting = __esm({
11962
11951
  });
11963
11952
  return data.total_count;
11964
11953
  } catch (error) {
11965
- const errorMessage = error instanceof Error ? error.message : String(error);
11966
- warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${errorMessage}. Defaulting to 0.`);
11954
+ const errMsg = errorMessage(error);
11955
+ warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${errMsg}. Defaulting to 0.`);
11967
11956
  return 0;
11968
11957
  }
11969
11958
  }
@@ -12006,12 +11995,9 @@ var init_issue_vetting = __esm({
12006
11995
  }
12007
11996
  return { passed: true };
12008
11997
  } catch (error) {
12009
- const errorMessage = error instanceof Error ? error.message : String(error);
12010
- warn(
12011
- MODULE8,
12012
- `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errorMessage}. Assuming not claimed.`
12013
- );
12014
- return { passed: true, inconclusive: true, reason: errorMessage };
11998
+ const errMsg = errorMessage(error);
11999
+ warn(MODULE8, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming not claimed.`);
12000
+ return { passed: true, inconclusive: true, reason: errMsg };
12015
12001
  }
12016
12002
  }
12017
12003
  async checkProjectHealth(owner, repo) {
@@ -12042,8 +12028,8 @@ var init_issue_vetting = __esm({
12042
12028
  ciStatus = "passing";
12043
12029
  }
12044
12030
  } catch (error) {
12045
- const errorMessage = error instanceof Error ? error.message : String(error);
12046
- warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${errorMessage}. Defaulting to unknown.`);
12031
+ const errMsg = errorMessage(error);
12032
+ warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${errMsg}. Defaulting to unknown.`);
12047
12033
  }
12048
12034
  return {
12049
12035
  repo: `${owner}/${repo}`,
@@ -12058,8 +12044,8 @@ var init_issue_vetting = __esm({
12058
12044
  forksCount: repoData.forks_count
12059
12045
  };
12060
12046
  } catch (error) {
12061
- const errorMessage = error instanceof Error ? error.message : String(error);
12062
- warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${errorMessage}`);
12047
+ const errMsg = errorMessage(error);
12048
+ warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${errMsg}`);
12063
12049
  return {
12064
12050
  repo: `${owner}/${repo}`,
12065
12051
  lastCommitAt: "",
@@ -12069,7 +12055,7 @@ var init_issue_vetting = __esm({
12069
12055
  ciStatus: "unknown",
12070
12056
  isActive: false,
12071
12057
  checkFailed: true,
12072
- failureReason: errorMessage
12058
+ failureReason: errMsg
12073
12059
  };
12074
12060
  }
12075
12061
  }
@@ -12222,18 +12208,18 @@ var init_issue_discovery = __esm({
12222
12208
  return starredRepos;
12223
12209
  } catch (error) {
12224
12210
  const cachedRepos = this.stateManager.getStarredRepos();
12225
- const errorMessage = error instanceof Error ? error.message : String(error);
12226
- warn(MODULE9, "Error fetching starred repos:", errorMessage);
12211
+ const errMsg = errorMessage(error);
12212
+ warn(MODULE9, "Error fetching starred repos:", errMsg);
12227
12213
  if (cachedRepos.length === 0) {
12228
12214
  warn(
12229
12215
  MODULE9,
12230
- `Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${errorMessage}
12216
+ `Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${errMsg}
12231
12217
  Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
12232
12218
  );
12233
12219
  } else {
12234
12220
  warn(
12235
12221
  MODULE9,
12236
- `Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${errorMessage}`
12222
+ `Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${errMsg}`
12237
12223
  );
12238
12224
  }
12239
12225
  return cachedRepos;
@@ -12272,10 +12258,10 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
12272
12258
  warn(MODULE9, this.rateLimitWarning);
12273
12259
  }
12274
12260
  } catch (error) {
12275
- if (error?.status === 401) {
12261
+ if (getHttpStatusCode(error) === 401) {
12276
12262
  throw error;
12277
12263
  }
12278
- warn(MODULE9, "Could not check rate limit:", error instanceof Error ? error.message : error);
12264
+ warn(MODULE9, "Could not check rate limit:", errorMessage(error));
12279
12265
  }
12280
12266
  const mergedPRRepos = this.stateManager.getReposWithMergedPRs();
12281
12267
  const mergedPRRepoSet = new Set(mergedPRRepos);
@@ -12441,12 +12427,12 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
12441
12427
  }
12442
12428
  console.log(`Found ${starFiltered.length} candidates from general search`);
12443
12429
  } catch (error) {
12444
- const errorMessage = error instanceof Error ? error.message : String(error);
12445
- phase2Error = errorMessage;
12430
+ const errMsg = errorMessage(error);
12431
+ phase2Error = errMsg;
12446
12432
  if (IssueVetter.isRateLimitError(error)) {
12447
12433
  rateLimitHitDuringSearch = true;
12448
12434
  }
12449
- warn(MODULE9, `Error in general issue search: ${errorMessage}`);
12435
+ warn(MODULE9, `Error in general issue search: ${errMsg}`);
12450
12436
  }
12451
12437
  }
12452
12438
  let phase3Error = null;
@@ -12510,12 +12496,12 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
12510
12496
  }
12511
12497
  console.log(`Found ${starFiltered.length} candidates from maintained-repo search`);
12512
12498
  } catch (error) {
12513
- const errorMessage = error instanceof Error ? error.message : String(error);
12514
- phase3Error = errorMessage;
12499
+ const errMsg = errorMessage(error);
12500
+ phase3Error = errMsg;
12515
12501
  if (IssueVetter.isRateLimitError(error)) {
12516
12502
  rateLimitHitDuringSearch = true;
12517
12503
  }
12518
- warn(MODULE9, `Error in maintained-repo search: ${errorMessage}`);
12504
+ warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
12519
12505
  }
12520
12506
  }
12521
12507
  if (allCandidates.length === 0) {
@@ -12591,11 +12577,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
12591
12577
  rateLimitFailures++;
12592
12578
  }
12593
12579
  const batchRepos = batch.join(", ");
12594
- warn(
12595
- MODULE9,
12596
- `Error searching issues in batch [${batchRepos}]:`,
12597
- error instanceof Error ? error.message : error
12598
- );
12580
+ warn(MODULE9, `Error searching issues in batch [${batchRepos}]:`, errorMessage(error));
12599
12581
  }
12600
12582
  }
12601
12583
  const allBatchesFailed = failedBatches === batches.length && batches.length > 0;
@@ -12789,7 +12771,7 @@ var init_issue_conversation = __esm({
12789
12771
  }
12790
12772
  const { owner, repo } = parsed;
12791
12773
  const repoFullName = `${owner}/${repo}`;
12792
- if (owner.toLowerCase() === username.toLowerCase()) continue;
12774
+ if (isOwnRepo(owner, username)) continue;
12793
12775
  if (item.user?.login?.toLowerCase() === username.toLowerCase()) continue;
12794
12776
  if (config.excludeRepos.includes(repoFullName)) continue;
12795
12777
  if (config.excludeOrgs?.some((org) => owner.toLowerCase() === org.toLowerCase())) continue;
@@ -12814,7 +12796,7 @@ var init_issue_conversation = __esm({
12814
12796
  });
12815
12797
  }
12816
12798
  } catch (error) {
12817
- const msg = error instanceof Error ? error.message : String(error);
12799
+ const msg = errorMessage(error);
12818
12800
  failures.push({ issueUrl: item.html_url, error: msg });
12819
12801
  warn(MODULE10, `Error analyzing issue ${item.html_url}: ${msg}`);
12820
12802
  }
@@ -13368,6 +13350,7 @@ var init_core = __esm({
13368
13350
  init_comment_utils();
13369
13351
  init_github();
13370
13352
  init_utils();
13353
+ init_errors();
13371
13354
  init_logger();
13372
13355
  init_http_cache();
13373
13356
  init_daily_logic();
@@ -13455,25 +13438,9 @@ __export(daily_exports, {
13455
13438
  groupPRsByRepo: () => groupPRsByRepo,
13456
13439
  printDigest: () => printDigest,
13457
13440
  runDaily: () => runDaily,
13441
+ runDailyForDisplay: () => runDailyForDisplay,
13458
13442
  toShelvedPRRef: () => toShelvedPRRef
13459
13443
  });
13460
- async function runDaily(options) {
13461
- const token = getGitHubToken();
13462
- try {
13463
- await runDailyInner(token, options);
13464
- } catch (error) {
13465
- const msg = error instanceof Error ? error.message : String(error);
13466
- if (options.json) {
13467
- outputJsonError(`Daily check failed: ${msg}`);
13468
- } else {
13469
- console.error(`[FATAL] Daily check failed: ${msg}`);
13470
- if (error instanceof Error && error.stack) {
13471
- console.error(error.stack);
13472
- }
13473
- }
13474
- process.exit(1);
13475
- }
13476
- }
13477
13444
  async function fetchPRData(prMonitor, token) {
13478
13445
  const { prs, failures } = await prMonitor.fetchUserOpenPRs();
13479
13446
  if (failures.length > 0) {
@@ -13485,15 +13452,15 @@ async function fetchPRData(prMonitor, token) {
13485
13452
  prMonitor.fetchUserMergedPRCounts(),
13486
13453
  prMonitor.fetchUserClosedPRCounts(),
13487
13454
  prMonitor.fetchRecentlyClosedPRs().catch((err) => {
13488
- console.error(`Warning: Failed to fetch recently closed PRs: ${err instanceof Error ? err.message : err}`);
13455
+ console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
13489
13456
  return [];
13490
13457
  }),
13491
13458
  prMonitor.fetchRecentlyMergedPRs().catch((err) => {
13492
- console.error(`Warning: Failed to fetch recently merged PRs: ${err instanceof Error ? err.message : err}`);
13459
+ console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
13493
13460
  return [];
13494
13461
  }),
13495
13462
  issueMonitor.fetchCommentedIssues().catch((error) => {
13496
- const msg = error instanceof Error ? error.message : String(error);
13463
+ const msg = errorMessage(error);
13497
13464
  if (msg.includes("No GitHub username configured")) {
13498
13465
  console.error(`[DAILY] Issue conversation tracking requires setup: ${msg}`);
13499
13466
  } else {
@@ -13550,10 +13517,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13550
13517
  stateManager2.updateRepoScore(repo, { mergedPRCount: count, lastMergedAt: lastMergedAt || void 0 });
13551
13518
  } catch (error) {
13552
13519
  mergedCountFailures++;
13553
- console.error(
13554
- `[DAILY] Failed to update merged count for ${repo}:`,
13555
- error instanceof Error ? error.message : error
13556
- );
13520
+ console.error(`[DAILY] Failed to update merged count for ${repo}:`, errorMessage(error));
13557
13521
  }
13558
13522
  }
13559
13523
  if (mergedCountFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -13573,10 +13537,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13573
13537
  stateManager2.updateRepoScore(repo, { closedWithoutMergeCount: count });
13574
13538
  } catch (error) {
13575
13539
  closedCountFailures++;
13576
- console.error(
13577
- `[DAILY] Failed to update closed count for ${repo}:`,
13578
- error instanceof Error ? error.message : error
13579
- );
13540
+ console.error(`[DAILY] Failed to update closed count for ${repo}:`, errorMessage(error));
13580
13541
  }
13581
13542
  }
13582
13543
  if (closedCountFailures === closedCounts.size && closedCounts.size > 0) {
@@ -13589,7 +13550,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13589
13550
  stateManager2.updateRepoScore(repo, { signals });
13590
13551
  } catch (error) {
13591
13552
  signalUpdateFailures++;
13592
- console.error(`[DAILY] Failed to update signals for ${repo}:`, error instanceof Error ? error.message : error);
13553
+ console.error(`[DAILY] Failed to update signals for ${repo}:`, errorMessage(error));
13593
13554
  }
13594
13555
  }
13595
13556
  if (signalUpdateFailures === repoSignals.size && repoSignals.size > 0) {
@@ -13602,7 +13563,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13602
13563
  try {
13603
13564
  starCounts = await prMonitor.fetchRepoStarCounts(allRepos);
13604
13565
  } catch (error) {
13605
- console.error("[DAILY] Failed to fetch repo star counts:", error instanceof Error ? error.message : error);
13566
+ console.error("[DAILY] Failed to fetch repo star counts:", errorMessage(error));
13606
13567
  console.error(
13607
13568
  "[DAILY] Dashboard minStars filter will use cached star counts (or be skipped for repos without cached data)."
13608
13569
  );
@@ -13614,7 +13575,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13614
13575
  stateManager2.updateRepoScore(repo, { stargazersCount: stars });
13615
13576
  } catch (error) {
13616
13577
  starUpdateFailures++;
13617
- console.error(`[DAILY] Failed to update star count for ${repo}:`, error instanceof Error ? error.message : error);
13578
+ console.error(`[DAILY] Failed to update star count for ${repo}:`, errorMessage(error));
13618
13579
  }
13619
13580
  }
13620
13581
  if (starUpdateFailures === starCounts.size && starCounts.size > 0) {
@@ -13626,7 +13587,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
13626
13587
  stateManager2.addTrustedProject(repo);
13627
13588
  } catch (error) {
13628
13589
  trustSyncFailures++;
13629
- console.error(`[DAILY] Failed to sync trusted project ${repo}:`, error instanceof Error ? error.message : error);
13590
+ console.error(`[DAILY] Failed to sync trusted project ${repo}:`, errorMessage(error));
13630
13591
  }
13631
13592
  }
13632
13593
  if (trustSyncFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -13640,12 +13601,12 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
13640
13601
  try {
13641
13602
  stateManager2.setMonthlyMergedCounts(monthlyCounts);
13642
13603
  } catch (error) {
13643
- console.error("[DAILY] Failed to store monthly merged counts:", error instanceof Error ? error.message : error);
13604
+ console.error("[DAILY] Failed to store monthly merged counts:", errorMessage(error));
13644
13605
  }
13645
13606
  try {
13646
13607
  stateManager2.setMonthlyClosedCounts(monthlyClosedCounts);
13647
13608
  } catch (error) {
13648
- console.error("[DAILY] Failed to store monthly closed counts:", error instanceof Error ? error.message : error);
13609
+ console.error("[DAILY] Failed to store monthly closed counts:", errorMessage(error));
13649
13610
  }
13650
13611
  try {
13651
13612
  const combinedOpenedCounts = { ...openedFromMerged };
@@ -13660,10 +13621,7 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
13660
13621
  }
13661
13622
  stateManager2.setMonthlyOpenedCounts(combinedOpenedCounts);
13662
13623
  } catch (error) {
13663
- console.error(
13664
- "[DAILY] Failed to compute/store monthly opened counts:",
13665
- error instanceof Error ? error.message : error
13666
- );
13624
+ console.error("[DAILY] Failed to compute/store monthly opened counts:", errorMessage(error));
13667
13625
  }
13668
13626
  }
13669
13627
  function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
@@ -13678,7 +13636,7 @@ function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
13678
13636
  stateManager2.save();
13679
13637
  }
13680
13638
  } catch (error) {
13681
- console.error("[DAILY] Failed to expire/persist snoozes:", error instanceof Error ? error.message : error);
13639
+ console.error("[DAILY] Failed to expire/persist snoozes:", errorMessage(error));
13682
13640
  }
13683
13641
  const shelvedPRs = [];
13684
13642
  const autoUnshelvedPRs = [];
@@ -13796,19 +13754,19 @@ async function executeDailyCheckInternal(token) {
13796
13754
  const { activePRs, shelvedPRs, digest } = partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs);
13797
13755
  return generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, failures);
13798
13756
  }
13799
- async function runDailyInner(token, options) {
13800
- if (options.json) {
13801
- const result = await executeDailyCheck(token);
13802
- outputJson(result);
13803
- } else {
13804
- const result = await executeDailyCheckInternal(token);
13805
- printDigest(result.digest, result.capacity, result.commentedIssues);
13806
- }
13757
+ async function runDaily() {
13758
+ const token = requireGitHubToken();
13759
+ return executeDailyCheck(token);
13760
+ }
13761
+ async function runDailyForDisplay() {
13762
+ const token = requireGitHubToken();
13763
+ return executeDailyCheckInternal(token);
13807
13764
  }
13808
13765
  var init_daily = __esm({
13809
13766
  "src/commands/daily.ts"() {
13810
13767
  "use strict";
13811
13768
  init_core();
13769
+ init_errors();
13812
13770
  init_json();
13813
13771
  init_core();
13814
13772
  }
@@ -13824,39 +13782,21 @@ async function runStatus(options) {
13824
13782
  const stats = stateManager2.getStats();
13825
13783
  const state = stateManager2.getState();
13826
13784
  const lastUpdated = state.lastDigestAt || state.lastRunAt;
13827
- if (options.json) {
13828
- const { totalTracked: _totalTracked, ...outputStats } = stats;
13829
- const output = {
13830
- stats: outputStats,
13831
- lastRunAt: state.lastRunAt
13832
- };
13833
- if (options.offline) {
13834
- output.offline = true;
13835
- output.lastUpdated = lastUpdated;
13836
- }
13837
- outputJson(output);
13838
- } else {
13839
- console.log("\n\u{1F4CA} OSS Status\n");
13840
- console.log(`Merged PRs: ${stats.mergedPRs}`);
13841
- console.log(`Closed PRs: ${stats.closedPRs}`);
13842
- console.log(`Merge Rate: ${stats.mergeRate}`);
13843
- console.log(`Needs Response: ${stats.needsResponse}`);
13844
- if (options.offline) {
13845
- console.log(`
13846
- Last Updated: ${lastUpdated || "Never"}`);
13847
- console.log("(Offline mode: showing cached data)");
13848
- } else {
13849
- console.log(`
13850
- Last Run: ${state.lastRunAt || "Never"}`);
13851
- }
13852
- console.log("\nRun with --json for structured output");
13785
+ const { totalTracked: _totalTracked, ...outputStats } = stats;
13786
+ const output = {
13787
+ stats: outputStats,
13788
+ lastRunAt: state.lastRunAt
13789
+ };
13790
+ if (options.offline) {
13791
+ output.offline = true;
13792
+ output.lastUpdated = lastUpdated;
13853
13793
  }
13794
+ return output;
13854
13795
  }
13855
13796
  var init_status = __esm({
13856
13797
  "src/commands/status.ts"() {
13857
13798
  "use strict";
13858
13799
  init_core();
13859
- init_json();
13860
13800
  }
13861
13801
  });
13862
13802
 
@@ -13866,104 +13806,68 @@ __export(search_exports, {
13866
13806
  runSearch: () => runSearch
13867
13807
  });
13868
13808
  async function runSearch(options) {
13869
- const token = getGitHubToken();
13809
+ const token = requireGitHubToken();
13870
13810
  const discovery = new IssueDiscovery(token);
13871
- if (!options.json) {
13872
- console.log(`
13873
- \u{1F50D} Searching for issues (max ${options.maxResults})...
13874
- `);
13875
- }
13876
13811
  const candidates = await discovery.searchIssues({ maxResults: options.maxResults });
13877
- if (options.json) {
13878
- const stateManager2 = getStateManager();
13879
- const { config } = stateManager2.getState();
13880
- const excludedRepos = config.excludeRepos || [];
13881
- const aiPolicyBlocklist = config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? [];
13882
- const searchOutput = {
13883
- candidates: candidates.map((c) => {
13884
- const repoScoreRecord = stateManager2.getRepoScore(c.issue.repo);
13885
- return {
13886
- issue: {
13887
- repo: c.issue.repo,
13888
- number: c.issue.number,
13889
- title: c.issue.title,
13890
- url: c.issue.url,
13891
- labels: c.issue.labels
13892
- },
13893
- recommendation: c.recommendation,
13894
- reasonsToApprove: c.reasonsToApprove,
13895
- reasonsToSkip: c.reasonsToSkip,
13896
- searchPriority: c.searchPriority,
13897
- viabilityScore: c.viabilityScore,
13898
- repoScore: repoScoreRecord ? {
13899
- score: repoScoreRecord.score,
13900
- mergedPRCount: repoScoreRecord.mergedPRCount,
13901
- closedWithoutMergeCount: repoScoreRecord.closedWithoutMergeCount,
13902
- isResponsive: repoScoreRecord.signals?.isResponsive ?? false,
13903
- lastMergedAt: repoScoreRecord.lastMergedAt
13904
- } : void 0
13905
- };
13906
- }),
13907
- excludedRepos,
13908
- aiPolicyBlocklist
13909
- };
13910
- if (discovery.rateLimitWarning) {
13911
- searchOutput.rateLimitWarning = discovery.rateLimitWarning;
13912
- }
13913
- outputJson(searchOutput);
13914
- } else {
13915
- if (candidates.length === 0) {
13916
- if (discovery.rateLimitWarning) {
13917
- console.warn(`
13918
- \u26A0 ${discovery.rateLimitWarning}
13919
- `);
13920
- } else {
13921
- console.log("No matching issues found.");
13922
- }
13923
- return;
13924
- }
13925
- if (discovery.rateLimitWarning) {
13926
- console.warn(`
13927
- \u26A0 ${discovery.rateLimitWarning}
13928
- `);
13929
- }
13930
- console.log(`Found ${candidates.length} candidates:
13931
- `);
13932
- for (const candidate of candidates) {
13933
- console.log(discovery.formatCandidate(candidate));
13934
- console.log("---");
13935
- }
13812
+ const stateManager2 = getStateManager();
13813
+ const { config } = stateManager2.getState();
13814
+ const excludedRepos = config.excludeRepos || [];
13815
+ const aiPolicyBlocklist = config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? [];
13816
+ const searchOutput = {
13817
+ candidates: candidates.map((c) => {
13818
+ const repoScoreRecord = stateManager2.getRepoScore(c.issue.repo);
13819
+ return {
13820
+ issue: {
13821
+ repo: c.issue.repo,
13822
+ number: c.issue.number,
13823
+ title: c.issue.title,
13824
+ url: c.issue.url,
13825
+ labels: c.issue.labels
13826
+ },
13827
+ recommendation: c.recommendation,
13828
+ reasonsToApprove: c.reasonsToApprove,
13829
+ reasonsToSkip: c.reasonsToSkip,
13830
+ searchPriority: c.searchPriority,
13831
+ viabilityScore: c.viabilityScore,
13832
+ repoScore: repoScoreRecord ? {
13833
+ score: repoScoreRecord.score,
13834
+ mergedPRCount: repoScoreRecord.mergedPRCount,
13835
+ closedWithoutMergeCount: repoScoreRecord.closedWithoutMergeCount,
13836
+ isResponsive: repoScoreRecord.signals?.isResponsive ?? false,
13837
+ lastMergedAt: repoScoreRecord.lastMergedAt
13838
+ } : void 0
13839
+ };
13840
+ }),
13841
+ excludedRepos,
13842
+ aiPolicyBlocklist
13843
+ };
13844
+ if (discovery.rateLimitWarning) {
13845
+ searchOutput.rateLimitWarning = discovery.rateLimitWarning;
13936
13846
  }
13847
+ return searchOutput;
13937
13848
  }
13938
13849
  var init_search = __esm({
13939
13850
  "src/commands/search.ts"() {
13940
13851
  "use strict";
13941
13852
  init_core();
13942
- init_json();
13943
13853
  }
13944
13854
  });
13945
13855
 
13946
13856
  // src/commands/validation.ts
13947
- function validateGitHubUrl(url, pattern, entityType, json) {
13857
+ function validateGitHubUrl(url, pattern, entityType) {
13948
13858
  if (pattern.test(url)) return;
13949
13859
  const example = entityType === "PR" ? "https://github.com/owner/repo/pull/123" : "https://github.com/owner/repo/issues/123";
13950
- const msg = `Invalid ${entityType} URL: ${url}. Expected format: ${example}`;
13951
- if (json) {
13952
- outputJsonError(msg);
13953
- } else {
13954
- console.error(`Error: ${msg}`);
13955
- }
13956
- process.exit(1);
13860
+ throw new ValidationError(`Invalid ${entityType} URL: ${url}. Expected format: ${example}`);
13957
13861
  }
13958
13862
  function validateUrl(url) {
13959
13863
  if (url.length > MAX_URL_LENGTH) {
13960
- throw new Error(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
13864
+ throw new ValidationError(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
13961
13865
  }
13962
13866
  return url;
13963
13867
  }
13964
13868
  function validateMessage(message) {
13965
13869
  if (message.length > MAX_MESSAGE_LENGTH) {
13966
- throw new Error(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
13870
+ throw new ValidationError(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
13967
13871
  }
13968
13872
  return message;
13969
13873
  }
@@ -13996,7 +13900,6 @@ var init_validation = __esm({
13996
13900
  "src/commands/validation.ts"() {
13997
13901
  "use strict";
13998
13902
  init_errors();
13999
- init_json();
14000
13903
  PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
14001
13904
  ISSUE_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
14002
13905
  MAX_URL_LENGTH = 2048;
@@ -14014,38 +13917,28 @@ __export(vet_exports, {
14014
13917
  });
14015
13918
  async function runVet(options) {
14016
13919
  validateUrl(options.issueUrl);
14017
- const token = getGitHubToken();
13920
+ const token = requireGitHubToken();
14018
13921
  const discovery = new IssueDiscovery(token);
14019
- if (!options.json) {
14020
- console.log(`
14021
- \u{1F50D} Vetting issue: ${options.issueUrl}
14022
- `);
14023
- }
14024
13922
  const candidate = await discovery.vetIssue(options.issueUrl);
14025
- if (options.json) {
14026
- outputJson({
14027
- issue: {
14028
- repo: candidate.issue.repo,
14029
- number: candidate.issue.number,
14030
- title: candidate.issue.title,
14031
- url: candidate.issue.url,
14032
- labels: candidate.issue.labels
14033
- },
14034
- recommendation: candidate.recommendation,
14035
- reasonsToApprove: candidate.reasonsToApprove,
14036
- reasonsToSkip: candidate.reasonsToSkip,
14037
- projectHealth: candidate.projectHealth,
14038
- vettingResult: candidate.vettingResult
14039
- });
14040
- } else {
14041
- console.log(discovery.formatCandidate(candidate));
14042
- }
13923
+ return {
13924
+ issue: {
13925
+ repo: candidate.issue.repo,
13926
+ number: candidate.issue.number,
13927
+ title: candidate.issue.title,
13928
+ url: candidate.issue.url,
13929
+ labels: candidate.issue.labels
13930
+ },
13931
+ recommendation: candidate.recommendation,
13932
+ reasonsToApprove: candidate.reasonsToApprove,
13933
+ reasonsToSkip: candidate.reasonsToSkip,
13934
+ projectHealth: candidate.projectHealth,
13935
+ vettingResult: candidate.vettingResult
13936
+ };
14043
13937
  }
14044
13938
  var init_vet = __esm({
14045
13939
  "src/commands/vet.ts"() {
14046
13940
  "use strict";
14047
13941
  init_core();
14048
- init_json();
14049
13942
  init_validation();
14050
13943
  }
14051
13944
  });
@@ -14058,59 +13951,37 @@ __export(track_exports, {
14058
13951
  });
14059
13952
  async function runTrack(options) {
14060
13953
  validateUrl(options.prUrl);
14061
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
14062
- const token = getGitHubToken();
13954
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
13955
+ const token = requireGitHubToken();
14063
13956
  const octokit = getOctokit(token);
14064
13957
  const parsed = parseGitHubUrl(options.prUrl);
14065
13958
  if (!parsed || parsed.type !== "pull") {
14066
- if (options.json) {
14067
- outputJsonError(`Invalid PR URL: ${options.prUrl}`);
14068
- } else {
14069
- console.error(`Error: Invalid PR URL: ${options.prUrl}`);
14070
- }
14071
- process.exit(1);
13959
+ throw new Error(`Invalid PR URL: ${options.prUrl}`);
14072
13960
  }
14073
13961
  const { owner, repo, number } = parsed;
14074
- if (!options.json) {
14075
- console.log(`
14076
- \u{1F4CC} Fetching PR: ${options.prUrl}
14077
- `);
14078
- }
14079
13962
  const { data: ghPR } = await octokit.pulls.get({ owner, repo, pull_number: number });
14080
- const pr = {
14081
- repo: `${owner}/${repo}`,
14082
- number,
14083
- title: ghPR.title,
14084
- url: options.prUrl
13963
+ return {
13964
+ pr: {
13965
+ repo: `${owner}/${repo}`,
13966
+ number,
13967
+ title: ghPR.title,
13968
+ url: options.prUrl
13969
+ }
14085
13970
  };
14086
- if (options.json) {
14087
- outputJson({ pr });
14088
- } else {
14089
- console.log(`PR: ${pr.repo}#${pr.number} - ${pr.title}`);
14090
- console.log("Note: In v2, PRs are tracked automatically via the daily run.");
14091
- }
14092
13971
  }
14093
13972
  async function runUntrack(options) {
14094
13973
  validateUrl(options.prUrl);
14095
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
14096
- if (options.json) {
14097
- outputJson({
14098
- removed: false,
14099
- url: options.prUrl,
14100
- message: "In v2, PRs are fetched fresh on each daily run \u2014 there is no local tracking list to remove from."
14101
- });
14102
- } else {
14103
- console.log(
14104
- "Note: In v2, PRs are fetched fresh on each daily run \u2014 there is no local tracking list to remove from."
14105
- );
14106
- console.log("Use `shelve` to temporarily hide a PR from the daily summary.");
14107
- }
13974
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
13975
+ return {
13976
+ removed: false,
13977
+ url: options.prUrl,
13978
+ message: "In v2, PRs are fetched fresh on each daily run \u2014 there is no local tracking list to remove from."
13979
+ };
14108
13980
  }
14109
13981
  var init_track = __esm({
14110
13982
  "src/commands/track.ts"() {
14111
13983
  "use strict";
14112
13984
  init_core();
14113
- init_json();
14114
13985
  init_validation();
14115
13986
  init_utils();
14116
13987
  }
@@ -14123,30 +13994,19 @@ __export(read_exports, {
14123
13994
  });
14124
13995
  async function runRead(options) {
14125
13996
  if (!options.all && !options.prUrl) {
14126
- if (options.json) {
14127
- outputJsonError("PR URL or --all flag required");
14128
- } else {
14129
- console.error("Usage: oss-autopilot read <pr-url> or oss-autopilot read --all");
14130
- }
14131
- process.exit(1);
13997
+ throw new Error("PR URL or --all flag required");
14132
13998
  }
14133
13999
  if (options.prUrl) {
14134
14000
  validateUrl(options.prUrl);
14135
14001
  }
14136
- if (options.json) {
14137
- if (options.all) {
14138
- outputJson({ markedAsRead: 0, all: true, message: "In v2, PR read state is not tracked locally." });
14139
- } else {
14140
- outputJson({ marked: false, url: options.prUrl, message: "In v2, PR read state is not tracked locally." });
14141
- }
14142
- } else {
14143
- console.log("Note: In v2, PR read state is not tracked locally. PRs are fetched fresh on each daily run.");
14002
+ if (options.all) {
14003
+ return { markedAsRead: 0, all: true, message: "In v2, PR read state is not tracked locally." };
14144
14004
  }
14005
+ return { marked: false, url: options.prUrl, message: "In v2, PR read state is not tracked locally." };
14145
14006
  }
14146
14007
  var init_read = __esm({
14147
14008
  "src/commands/read.ts"() {
14148
14009
  "use strict";
14149
- init_json();
14150
14010
  init_validation();
14151
14011
  }
14152
14012
  });
@@ -14160,47 +14020,44 @@ __export(comments_exports, {
14160
14020
  });
14161
14021
  async function runComments(options) {
14162
14022
  validateUrl(options.prUrl);
14163
- const token = getGitHubToken();
14023
+ const token = requireGitHubToken();
14164
14024
  const stateManager2 = getStateManager();
14165
14025
  const octokit = getOctokit(token);
14166
14026
  const parsed = parseGitHubUrl(options.prUrl);
14167
14027
  if (!parsed || parsed.type !== "pull") {
14168
- if (options.json) {
14169
- outputJsonError("Invalid PR URL format");
14170
- } else {
14171
- console.error("Invalid PR URL format");
14172
- }
14173
- process.exit(1);
14028
+ throw new Error("Invalid PR URL format");
14174
14029
  }
14175
14030
  const { owner, repo, number: pull_number } = parsed;
14176
14031
  const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
14177
- const reviewComments = await paginateAll(
14178
- (page) => octokit.pulls.listReviewComments({
14179
- owner,
14180
- repo,
14181
- pull_number,
14182
- per_page: 100,
14183
- page
14184
- })
14185
- );
14186
- const issueComments = await paginateAll(
14187
- (page) => octokit.issues.listComments({
14188
- owner,
14189
- repo,
14190
- issue_number: pull_number,
14191
- per_page: 100,
14192
- page
14193
- })
14194
- );
14195
- const reviews = await paginateAll(
14196
- (page) => octokit.pulls.listReviews({
14197
- owner,
14198
- repo,
14199
- pull_number,
14200
- per_page: 100,
14201
- page
14202
- })
14203
- );
14032
+ const [reviewComments, issueComments, reviews] = await Promise.all([
14033
+ paginateAll(
14034
+ (page) => octokit.pulls.listReviewComments({
14035
+ owner,
14036
+ repo,
14037
+ pull_number,
14038
+ per_page: 100,
14039
+ page
14040
+ })
14041
+ ),
14042
+ paginateAll(
14043
+ (page) => octokit.issues.listComments({
14044
+ owner,
14045
+ repo,
14046
+ issue_number: pull_number,
14047
+ per_page: 100,
14048
+ page
14049
+ })
14050
+ ),
14051
+ paginateAll(
14052
+ (page) => octokit.pulls.listReviews({
14053
+ owner,
14054
+ repo,
14055
+ pull_number,
14056
+ per_page: 100,
14057
+ page
14058
+ })
14059
+ )
14060
+ ]);
14204
14061
  const username = stateManager2.getState().config.githubUsername;
14205
14062
  const filterComment = (c) => {
14206
14063
  if (!c.user) return false;
@@ -14211,240 +14068,104 @@ async function runComments(options) {
14211
14068
  const relevantReviewComments = reviewComments.filter(filterComment).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
14212
14069
  const relevantIssueComments = issueComments.filter(filterComment).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
14213
14070
  const relevantReviews = reviews.filter((r) => filterComment(r) && r.body && r.body.trim()).sort((a, b) => new Date(b.submitted_at || 0).getTime() - new Date(a.submitted_at || 0).getTime());
14214
- if (options.json) {
14215
- outputJson({
14216
- pr: {
14217
- title: pr.title,
14218
- state: pr.state,
14219
- mergeable: pr.mergeable,
14220
- head: pr.head.ref,
14221
- base: pr.base.ref,
14222
- url: pr.html_url
14223
- },
14224
- reviews: relevantReviews.map((r) => ({
14225
- user: r.user?.login,
14226
- state: r.state,
14227
- body: r.body,
14228
- submittedAt: r.submitted_at
14229
- })),
14230
- reviewComments: relevantReviewComments.map((c) => ({
14231
- user: c.user?.login,
14232
- body: c.body,
14233
- path: c.path,
14234
- createdAt: c.created_at
14235
- })),
14236
- issueComments: relevantIssueComments.map((c) => ({
14237
- user: c.user?.login,
14238
- body: c.body,
14239
- createdAt: c.created_at
14240
- })),
14241
- summary: {
14242
- reviewCount: relevantReviews.length,
14243
- inlineCommentCount: relevantReviewComments.length,
14244
- discussionCommentCount: relevantIssueComments.length
14245
- }
14246
- });
14247
- return;
14248
- }
14249
- console.log(`
14250
- \u{1F4AC} Fetching comments for: ${options.prUrl}
14251
- `);
14252
- console.log(`## ${pr.title}
14253
- `);
14254
- console.log(`**Status:** ${pr.state} | **Mergeable:** ${pr.mergeable ?? "checking..."}`);
14255
- console.log(`**Branch:** ${pr.head.ref} \u2192 ${pr.base.ref}`);
14256
- console.log(`**URL:** ${pr.html_url}
14257
- `);
14258
- if (relevantReviews.length > 0) {
14259
- console.log("### Reviews (newest first)\n");
14260
- for (const review of relevantReviews) {
14261
- const state = review.state === "APPROVED" ? "\u2705" : review.state === "CHANGES_REQUESTED" ? "\u274C" : "\u{1F4AC}";
14262
- const time = review.submitted_at ? formatRelativeTime(review.submitted_at) : "";
14263
- console.log(`${state} **@${review.user?.login}** (${review.state}) - ${time}`);
14264
- if (review.body) {
14265
- console.log(`> ${review.body.split("\n").join("\n> ")}
14266
- `);
14267
- }
14268
- }
14269
- }
14270
- if (relevantReviewComments.length > 0) {
14271
- console.log("### Inline Comments (newest first)\n");
14272
- for (const comment of relevantReviewComments) {
14273
- const time = formatRelativeTime(comment.created_at);
14274
- console.log(`**@${comment.user?.login}** on \`${comment.path}\` - ${time}`);
14275
- console.log(`> ${comment.body.split("\n").join("\n> ")}`);
14276
- if (comment.diff_hunk) {
14277
- console.log(`\`\`\`diff
14278
- ${comment.diff_hunk.slice(-500)}
14279
- \`\`\``);
14280
- }
14281
- console.log("");
14282
- }
14283
- }
14284
- if (relevantIssueComments.length > 0) {
14285
- console.log("### Discussion (newest first)\n");
14286
- for (const comment of relevantIssueComments) {
14287
- const time = formatRelativeTime(comment.created_at);
14288
- console.log(`**@${comment.user?.login}** - ${time}`);
14289
- console.log(`> ${comment.body?.split("\n").join("\n> ")}
14290
- `);
14071
+ return {
14072
+ pr: {
14073
+ title: pr.title,
14074
+ state: pr.state,
14075
+ mergeable: pr.mergeable,
14076
+ head: pr.head.ref,
14077
+ base: pr.base.ref,
14078
+ url: pr.html_url
14079
+ },
14080
+ reviews: relevantReviews.map((r) => ({
14081
+ user: r.user?.login,
14082
+ state: r.state,
14083
+ body: r.body ?? null,
14084
+ submittedAt: r.submitted_at ?? null
14085
+ })),
14086
+ reviewComments: relevantReviewComments.map((c) => ({
14087
+ user: c.user?.login,
14088
+ body: c.body,
14089
+ path: c.path,
14090
+ createdAt: c.created_at
14091
+ })),
14092
+ issueComments: relevantIssueComments.map((c) => ({
14093
+ user: c.user?.login,
14094
+ body: c.body,
14095
+ createdAt: c.created_at
14096
+ })),
14097
+ summary: {
14098
+ reviewCount: relevantReviews.length,
14099
+ inlineCommentCount: relevantReviewComments.length,
14100
+ discussionCommentCount: relevantIssueComments.length
14291
14101
  }
14292
- }
14293
- if (relevantReviewComments.length === 0 && relevantIssueComments.length === 0 && relevantReviews.length === 0) {
14294
- console.log("No comments from other users.\n");
14295
- }
14296
- console.log("---");
14297
- console.log(
14298
- `**Summary:** ${relevantReviews.length} reviews, ${relevantReviewComments.length} inline comments, ${relevantIssueComments.length} discussion comments`
14299
- );
14102
+ };
14300
14103
  }
14301
14104
  async function runPost(options) {
14302
14105
  validateUrl(options.url);
14303
- const token = getGitHubToken();
14304
- let message = options.message;
14305
- if (options.stdin) {
14306
- const chunks = [];
14307
- for await (const chunk of process.stdin) {
14308
- chunks.push(chunk);
14309
- }
14310
- message = Buffer.concat(chunks).toString("utf-8").trim();
14311
- }
14312
- if (!message) {
14313
- if (options.json) {
14314
- outputJsonError("No message provided");
14315
- } else {
14316
- console.error("Error: No message provided");
14317
- }
14318
- process.exit(1);
14319
- }
14320
- try {
14321
- validateMessage(message);
14322
- } catch (error) {
14323
- if (options.json) {
14324
- outputJsonError(error instanceof Error ? error.message : "Invalid message");
14325
- } else {
14326
- console.error(`Error: ${error instanceof Error ? error.message : "Invalid message"}`);
14327
- }
14328
- process.exit(1);
14106
+ if (!options.message.trim()) {
14107
+ throw new Error("No message provided");
14329
14108
  }
14109
+ validateMessage(options.message);
14110
+ const token = requireGitHubToken();
14330
14111
  const parsed = parseGitHubUrl(options.url);
14331
14112
  if (!parsed) {
14332
- if (options.json) {
14333
- outputJsonError("Invalid GitHub URL format");
14334
- } else {
14335
- console.error("Invalid GitHub URL format");
14336
- }
14337
- process.exit(1);
14113
+ throw new Error("Invalid GitHub URL format");
14338
14114
  }
14339
14115
  const { owner, repo, number } = parsed;
14340
14116
  const octokit = getOctokit(token);
14341
- if (!options.json) {
14342
- console.log("\n\u{1F4DD} Posting comment to:", options.url);
14343
- console.log("---");
14344
- console.log(message);
14345
- console.log("---\n");
14346
- }
14347
- try {
14348
- const { data: comment } = await octokit.issues.createComment({
14349
- owner,
14350
- repo,
14351
- issue_number: number,
14352
- body: message
14353
- });
14354
- if (options.json) {
14355
- outputJson({
14356
- commentUrl: comment.html_url,
14357
- url: options.url
14358
- });
14359
- } else {
14360
- console.log("\u2705 Comment posted successfully!");
14361
- console.log(` ${comment.html_url}`);
14362
- }
14363
- } catch (error) {
14364
- if (options.json) {
14365
- outputJsonError(error instanceof Error ? error.message : "Unknown error");
14366
- } else {
14367
- console.error("\u274C Failed to post comment:", error instanceof Error ? error.message : error);
14368
- }
14369
- process.exit(1);
14370
- }
14117
+ const { data: comment } = await octokit.issues.createComment({
14118
+ owner,
14119
+ repo,
14120
+ issue_number: number,
14121
+ body: options.message
14122
+ });
14123
+ return {
14124
+ commentUrl: comment.html_url,
14125
+ url: options.url
14126
+ };
14371
14127
  }
14372
14128
  async function runClaim(options) {
14373
14129
  validateUrl(options.issueUrl);
14374
- const token = getGitHubToken();
14130
+ const token = requireGitHubToken();
14375
14131
  const message = options.message || "Hi! I'd like to work on this issue. Could you assign it to me?";
14376
- try {
14377
- validateMessage(message);
14378
- } catch (error) {
14379
- if (options.json) {
14380
- outputJsonError(error instanceof Error ? error.message : "Invalid message");
14381
- } else {
14382
- console.error(`Error: ${error instanceof Error ? error.message : "Invalid message"}`);
14383
- }
14384
- process.exit(1);
14385
- }
14132
+ validateMessage(message);
14386
14133
  const parsed = parseGitHubUrl(options.issueUrl);
14387
14134
  if (!parsed || parsed.type !== "issues") {
14388
- if (options.json) {
14389
- outputJsonError("Invalid issue URL format (must be an issue, not a PR)");
14390
- } else {
14391
- console.error("Invalid issue URL format (must be an issue, not a PR)");
14392
- }
14393
- process.exit(1);
14135
+ throw new Error("Invalid issue URL format (must be an issue, not a PR)");
14394
14136
  }
14395
14137
  const { owner, repo, number } = parsed;
14396
- if (!options.json) {
14397
- console.log("\n\u{1F64B} Claiming issue:", options.issueUrl);
14398
- console.log("---");
14399
- console.log(message);
14400
- console.log("---\n");
14401
- }
14402
14138
  const octokit = getOctokit(token);
14403
- try {
14404
- const { data: comment } = await octokit.issues.createComment({
14405
- owner,
14406
- repo,
14407
- issue_number: number,
14408
- body: message
14409
- });
14410
- const stateManager2 = getStateManager();
14411
- stateManager2.addIssue({
14412
- id: number,
14413
- url: options.issueUrl,
14414
- repo: `${owner}/${repo}`,
14415
- number,
14416
- title: "(claimed)",
14417
- status: "claimed",
14418
- labels: [],
14419
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14420
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14421
- vetted: false
14422
- });
14423
- stateManager2.save();
14424
- if (options.json) {
14425
- outputJson({
14426
- commentUrl: comment.html_url,
14427
- issueUrl: options.issueUrl
14428
- });
14429
- } else {
14430
- console.log("\u2705 Issue claimed!");
14431
- console.log(` ${comment.html_url}`);
14432
- }
14433
- } catch (error) {
14434
- if (options.json) {
14435
- outputJsonError(error instanceof Error ? error.message : "Unknown error");
14436
- } else {
14437
- console.error("\u274C Failed to claim issue:", error instanceof Error ? error.message : error);
14438
- }
14439
- process.exit(1);
14440
- }
14139
+ const { data: comment } = await octokit.issues.createComment({
14140
+ owner,
14141
+ repo,
14142
+ issue_number: number,
14143
+ body: message
14144
+ });
14145
+ const stateManager2 = getStateManager();
14146
+ stateManager2.addIssue({
14147
+ id: number,
14148
+ url: options.issueUrl,
14149
+ repo: `${owner}/${repo}`,
14150
+ number,
14151
+ title: "(claimed)",
14152
+ status: "claimed",
14153
+ labels: [],
14154
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14155
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14156
+ vetted: false
14157
+ });
14158
+ stateManager2.save();
14159
+ return {
14160
+ commentUrl: comment.html_url,
14161
+ issueUrl: options.issueUrl
14162
+ };
14441
14163
  }
14442
14164
  var init_comments = __esm({
14443
14165
  "src/commands/comments.ts"() {
14444
14166
  "use strict";
14445
14167
  init_core();
14446
14168
  init_pagination();
14447
- init_json();
14448
14169
  init_validation();
14449
14170
  }
14450
14171
  });
@@ -14454,28 +14175,14 @@ var config_exports = {};
14454
14175
  __export(config_exports, {
14455
14176
  runConfig: () => runConfig
14456
14177
  });
14457
- function exitWithError(msg, json) {
14458
- if (json) {
14459
- outputJsonError(msg);
14460
- } else {
14461
- console.error(msg);
14462
- }
14463
- process.exit(1);
14464
- }
14465
14178
  async function runConfig(options) {
14466
14179
  const stateManager2 = getStateManager();
14467
14180
  const currentConfig = stateManager2.getState().config;
14468
14181
  if (!options.key) {
14469
- if (options.json) {
14470
- outputJson({ config: currentConfig });
14471
- } else {
14472
- console.log("\n\u2699\uFE0F Current Configuration:\n");
14473
- console.log(JSON.stringify(currentConfig, null, 2));
14474
- }
14475
- return;
14182
+ return { config: currentConfig };
14476
14183
  }
14477
14184
  if (!options.value) {
14478
- exitWithError("Value required", options.json);
14185
+ throw new Error("Value required");
14479
14186
  }
14480
14187
  const value = options.value;
14481
14188
  switch (options.key) {
@@ -14495,9 +14202,8 @@ async function runConfig(options) {
14495
14202
  case "exclude-repo": {
14496
14203
  const parts = value.split("/");
14497
14204
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
14498
- exitWithError(
14499
- `Invalid repo format "${value}". Use "owner/repo" format. To exclude an entire org, use: config exclude-org ${value}`,
14500
- options.json
14205
+ throw new Error(
14206
+ `Invalid repo format "${value}". Use "owner/repo" format. To exclude an entire org, use: config exclude-org ${value}`
14501
14207
  );
14502
14208
  }
14503
14209
  const valueLower = value.toLowerCase();
@@ -14509,9 +14215,8 @@ async function runConfig(options) {
14509
14215
  }
14510
14216
  case "exclude-org": {
14511
14217
  if (value.includes("/")) {
14512
- exitWithError(
14513
- `Invalid org name "${value}". Use just the org name (e.g., "facebook"), not "owner/repo" format. To exclude a specific repo, use: config exclude-repo ${value}`,
14514
- options.json
14218
+ throw new Error(
14219
+ `Invalid org name "${value}". Use just the org name (e.g., "facebook"), not "owner/repo" format. To exclude a specific repo, use: config exclude-repo ${value}`
14515
14220
  );
14516
14221
  }
14517
14222
  const currentOrgs = currentConfig.excludeOrgs ?? [];
@@ -14522,20 +14227,15 @@ async function runConfig(options) {
14522
14227
  break;
14523
14228
  }
14524
14229
  default:
14525
- exitWithError(`Unknown config key: ${options.key}`, options.json);
14230
+ throw new Error(`Unknown config key: ${options.key}`);
14526
14231
  }
14527
14232
  stateManager2.save();
14528
- if (options.json) {
14529
- outputJson({ success: true, key: options.key, value });
14530
- } else {
14531
- console.log(`Set ${options.key} to: ${value}`);
14532
- }
14233
+ return { success: true, key: options.key, value };
14533
14234
  }
14534
14235
  var init_config = __esm({
14535
14236
  "src/commands/config.ts"() {
14536
14237
  "use strict";
14537
14238
  init_core();
14538
- init_json();
14539
14239
  }
14540
14240
  });
14541
14241
 
@@ -14547,28 +14247,17 @@ __export(init_exports, {
14547
14247
  async function runInit(options) {
14548
14248
  validateGitHubUsername(options.username);
14549
14249
  const stateManager2 = getStateManager();
14550
- if (!options.json) {
14551
- console.log(`
14552
- \u{1F680} Initializing for @${options.username}...
14553
- `);
14554
- }
14555
14250
  stateManager2.updateConfig({ githubUsername: options.username });
14556
14251
  stateManager2.save();
14557
- if (options.json) {
14558
- outputJson({
14559
- username: options.username,
14560
- message: "Username saved. Run `daily` to fetch your open PRs from GitHub."
14561
- });
14562
- } else {
14563
- console.log(`Username set to @${options.username}.`);
14564
- console.log("Run `oss-autopilot daily` to fetch your open PRs from GitHub.");
14565
- }
14252
+ return {
14253
+ username: options.username,
14254
+ message: "Username saved. Run `daily` to fetch your open PRs from GitHub."
14255
+ };
14566
14256
  }
14567
14257
  var init_init = __esm({
14568
14258
  "src/commands/init.ts"() {
14569
14259
  "use strict";
14570
14260
  init_core();
14571
- init_json();
14572
14261
  init_validation();
14573
14262
  }
14574
14263
  });
@@ -14584,6 +14273,7 @@ async function runSetup(options) {
14584
14273
  const config = stateManager2.getState().config;
14585
14274
  if (options.set && options.set.length > 0) {
14586
14275
  const results = {};
14276
+ const warnings = [];
14587
14277
  for (const setting of options.set) {
14588
14278
  const [key, ...valueParts] = setting.split("=");
14589
14279
  const value = valueParts.join("=");
@@ -14649,15 +14339,11 @@ async function runSetup(options) {
14649
14339
  }
14650
14340
  }
14651
14341
  if (invalid.length > 0) {
14652
- if (!options.json) {
14653
- console.warn(`Warning: Skipping invalid entries (expected "owner/repo" format): ${invalid.join(", ")}`);
14654
- }
14342
+ warnings.push(`Warning: Skipping invalid entries (expected "owner/repo" format): ${invalid.join(", ")}`);
14655
14343
  results["aiPolicyBlocklist_invalidEntries"] = invalid.join(", ");
14656
14344
  }
14657
14345
  if (valid.length === 0 && entries.length > 0) {
14658
- if (!options.json) {
14659
- console.warn("Warning: All entries were invalid. Blocklist not updated.");
14660
- }
14346
+ warnings.push("Warning: All entries were invalid. Blocklist not updated.");
14661
14347
  results[key] = "(all entries invalid)";
14662
14348
  break;
14663
14349
  }
@@ -14672,175 +14358,91 @@ async function runSetup(options) {
14672
14358
  }
14673
14359
  break;
14674
14360
  default:
14675
- if (!options.json) {
14676
- console.warn(`Unknown setting: ${key}`);
14677
- }
14361
+ warnings.push(`Unknown setting: ${key}`);
14678
14362
  }
14679
14363
  }
14680
14364
  stateManager2.save();
14681
- if (options.json) {
14682
- outputJson({ success: true, settings: results });
14683
- } else {
14684
- for (const [key, value] of Object.entries(results)) {
14685
- console.log(`\u2713 ${key}: ${value}`);
14686
- }
14687
- }
14688
- return;
14365
+ return { success: true, settings: results, warnings: warnings.length > 0 ? warnings : void 0 };
14689
14366
  }
14690
14367
  if (config.setupComplete && !options.reset) {
14691
- if (options.json) {
14692
- outputJson({
14693
- setupComplete: true,
14694
- config: {
14695
- githubUsername: config.githubUsername,
14696
- maxActivePRs: config.maxActivePRs,
14697
- dormantThresholdDays: config.dormantThresholdDays,
14698
- approachingDormantDays: config.approachingDormantDays,
14699
- languages: config.languages,
14700
- labels: config.labels
14701
- }
14702
- });
14703
- } else {
14704
- console.log("\n\u2699\uFE0F OSS Autopilot Setup\n");
14705
- console.log("\u2713 Setup already complete!\n");
14706
- console.log("Current settings:");
14707
- console.log(` GitHub username: ${config.githubUsername || "(not set)"}`);
14708
- console.log(` Max active PRs: ${config.maxActivePRs}`);
14709
- console.log(` Dormant threshold: ${config.dormantThresholdDays} days`);
14710
- console.log(` Approaching dormant: ${config.approachingDormantDays} days`);
14711
- console.log(` Languages: ${config.languages.join(", ")}`);
14712
- console.log(` Labels: ${config.labels.join(", ")}`);
14713
- console.log(`
14714
- Run 'setup --reset' to reconfigure.`);
14715
- }
14716
- return;
14717
- }
14718
- if (options.json) {
14719
- outputJson({
14720
- setupRequired: true,
14721
- prompts: [
14722
- {
14723
- setting: "username",
14724
- prompt: "What is your GitHub username?",
14725
- current: config.githubUsername || null,
14726
- required: true,
14727
- type: "string"
14728
- },
14729
- {
14730
- setting: "maxActivePRs",
14731
- prompt: "How many PRs do you want to work on at once?",
14732
- current: config.maxActivePRs,
14733
- default: 10,
14734
- type: "number"
14735
- },
14736
- {
14737
- setting: "dormantDays",
14738
- prompt: "After how many days of inactivity should a PR be considered dormant?",
14739
- current: config.dormantThresholdDays,
14740
- default: 30,
14741
- type: "number"
14742
- },
14743
- {
14744
- setting: "approachingDays",
14745
- prompt: "At how many days should we warn about approaching dormancy?",
14746
- current: config.approachingDormantDays,
14747
- default: 25,
14748
- type: "number"
14749
- },
14750
- {
14751
- setting: "languages",
14752
- prompt: "What programming languages do you want to contribute to?",
14753
- current: config.languages,
14754
- default: ["typescript", "javascript"],
14755
- type: "list"
14756
- },
14757
- {
14758
- setting: "labels",
14759
- prompt: "What issue labels should we search for?",
14760
- current: config.labels,
14761
- default: ["good first issue", "help wanted"],
14762
- type: "list"
14763
- },
14764
- {
14765
- setting: "aiPolicyBlocklist",
14766
- prompt: "Repos with anti-AI contribution policies to block (owner/repo, comma-separated)?",
14767
- current: config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist,
14768
- default: ["matplotlib/matplotlib"],
14769
- type: "list"
14770
- }
14771
- ]
14772
- });
14773
- } else {
14774
- console.log("\n\u2699\uFE0F OSS Autopilot Setup\n");
14775
- console.log("SETUP_REQUIRED");
14776
- console.log("---");
14777
- console.log("Please configure the following settings:\n");
14778
- console.log("SETTING: username");
14779
- console.log("PROMPT: What is your GitHub username?");
14780
- console.log(`CURRENT: ${config.githubUsername || "(not set)"}`);
14781
- console.log("REQUIRED: true");
14782
- console.log("");
14783
- console.log("SETTING: maxActivePRs");
14784
- console.log("PROMPT: How many PRs do you want to work on at once?");
14785
- console.log(`CURRENT: ${config.maxActivePRs}`);
14786
- console.log("DEFAULT: 10");
14787
- console.log("TYPE: number");
14788
- console.log("");
14789
- console.log("SETTING: dormantDays");
14790
- console.log("PROMPT: After how many days of inactivity should a PR be considered dormant?");
14791
- console.log(`CURRENT: ${config.dormantThresholdDays}`);
14792
- console.log("DEFAULT: 30");
14793
- console.log("TYPE: number");
14794
- console.log("");
14795
- console.log("SETTING: approachingDays");
14796
- console.log("PROMPT: At how many days should we warn about approaching dormancy?");
14797
- console.log(`CURRENT: ${config.approachingDormantDays}`);
14798
- console.log("DEFAULT: 25");
14799
- console.log("TYPE: number");
14800
- console.log("");
14801
- console.log("SETTING: languages");
14802
- console.log("PROMPT: What programming languages do you want to contribute to? (comma-separated)");
14803
- console.log(`CURRENT: ${config.languages.join(", ")}`);
14804
- console.log("DEFAULT: typescript, javascript");
14805
- console.log("TYPE: list");
14806
- console.log("");
14807
- console.log("SETTING: labels");
14808
- console.log("PROMPT: What issue labels should we search for? (comma-separated)");
14809
- console.log(`CURRENT: ${config.labels.join(", ")}`);
14810
- console.log("DEFAULT: good first issue, help wanted");
14811
- console.log("TYPE: list");
14812
- console.log("");
14813
- console.log("SETTING: aiPolicyBlocklist");
14814
- console.log("PROMPT: Repos with anti-AI contribution policies to block? (owner/repo, comma-separated)");
14815
- console.log(`CURRENT: ${(config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? []).join(", ")}`);
14816
- console.log("DEFAULT: matplotlib/matplotlib");
14817
- console.log("TYPE: list");
14818
- console.log("");
14819
- console.log("---");
14820
- console.log("END_SETUP_PROMPTS");
14368
+ return {
14369
+ setupComplete: true,
14370
+ config: {
14371
+ githubUsername: config.githubUsername,
14372
+ maxActivePRs: config.maxActivePRs,
14373
+ dormantThresholdDays: config.dormantThresholdDays,
14374
+ approachingDormantDays: config.approachingDormantDays,
14375
+ languages: config.languages,
14376
+ labels: config.labels
14377
+ }
14378
+ };
14821
14379
  }
14380
+ return {
14381
+ setupRequired: true,
14382
+ prompts: [
14383
+ {
14384
+ setting: "username",
14385
+ prompt: "What is your GitHub username?",
14386
+ current: config.githubUsername || null,
14387
+ required: true,
14388
+ type: "string"
14389
+ },
14390
+ {
14391
+ setting: "maxActivePRs",
14392
+ prompt: "How many PRs do you want to work on at once?",
14393
+ current: config.maxActivePRs,
14394
+ default: 10,
14395
+ type: "number"
14396
+ },
14397
+ {
14398
+ setting: "dormantDays",
14399
+ prompt: "After how many days of inactivity should a PR be considered dormant?",
14400
+ current: config.dormantThresholdDays,
14401
+ default: 30,
14402
+ type: "number"
14403
+ },
14404
+ {
14405
+ setting: "approachingDays",
14406
+ prompt: "At how many days should we warn about approaching dormancy?",
14407
+ current: config.approachingDormantDays,
14408
+ default: 25,
14409
+ type: "number"
14410
+ },
14411
+ {
14412
+ setting: "languages",
14413
+ prompt: "What programming languages do you want to contribute to?",
14414
+ current: config.languages,
14415
+ default: ["typescript", "javascript"],
14416
+ type: "list"
14417
+ },
14418
+ {
14419
+ setting: "labels",
14420
+ prompt: "What issue labels should we search for?",
14421
+ current: config.labels,
14422
+ default: ["good first issue", "help wanted"],
14423
+ type: "list"
14424
+ },
14425
+ {
14426
+ setting: "aiPolicyBlocklist",
14427
+ prompt: "Repos with anti-AI contribution policies to block (owner/repo, comma-separated)?",
14428
+ current: config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? null,
14429
+ default: ["matplotlib/matplotlib"],
14430
+ type: "list"
14431
+ }
14432
+ ]
14433
+ };
14822
14434
  }
14823
- async function runCheckSetup(options) {
14435
+ async function runCheckSetup() {
14824
14436
  const stateManager2 = getStateManager();
14825
- if (options.json) {
14826
- outputJson({
14827
- setupComplete: stateManager2.isSetupComplete(),
14828
- username: stateManager2.getState().config.githubUsername
14829
- });
14830
- } else {
14831
- if (stateManager2.isSetupComplete()) {
14832
- console.log("SETUP_COMPLETE");
14833
- console.log(`username=${stateManager2.getState().config.githubUsername}`);
14834
- } else {
14835
- console.log("SETUP_INCOMPLETE");
14836
- }
14837
- }
14437
+ return {
14438
+ setupComplete: stateManager2.isSetupComplete(),
14439
+ username: stateManager2.getState().config.githubUsername
14440
+ };
14838
14441
  }
14839
14442
  var init_setup = __esm({
14840
14443
  "src/commands/setup.ts"() {
14841
14444
  "use strict";
14842
14445
  init_core();
14843
- init_json();
14844
14446
  init_validation();
14845
14447
  }
14846
14448
  });
@@ -14853,17 +14455,17 @@ async function fetchDashboardData(token) {
14853
14455
  const [{ prs, failures }, recentlyClosedPRs, recentlyMergedPRs, mergedResult, closedResult, fetchedIssues] = await Promise.all([
14854
14456
  prMonitor.fetchUserOpenPRs(),
14855
14457
  prMonitor.fetchRecentlyClosedPRs().catch((err) => {
14856
- console.error(`Warning: Failed to fetch recently closed PRs: ${err instanceof Error ? err.message : err}`);
14458
+ console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
14857
14459
  return [];
14858
14460
  }),
14859
14461
  prMonitor.fetchRecentlyMergedPRs().catch((err) => {
14860
- console.error(`Warning: Failed to fetch recently merged PRs: ${err instanceof Error ? err.message : err}`);
14462
+ console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
14861
14463
  return [];
14862
14464
  }),
14863
14465
  prMonitor.fetchUserMergedPRCounts(),
14864
14466
  prMonitor.fetchUserClosedPRCounts(),
14865
14467
  issueMonitor.fetchCommentedIssues().catch((error) => {
14866
- const msg = error instanceof Error ? error.message : String(error);
14468
+ const msg = errorMessage(error);
14867
14469
  if (msg.includes("No GitHub username configured")) {
14868
14470
  console.error(`[DASHBOARD] Issue conversation tracking requires setup: ${msg}`);
14869
14471
  } else {
@@ -14887,12 +14489,12 @@ async function fetchDashboardData(token) {
14887
14489
  try {
14888
14490
  stateManager2.setMonthlyMergedCounts(monthlyCounts);
14889
14491
  } catch (error) {
14890
- console.error("[DASHBOARD] Failed to store monthly merged counts:", error instanceof Error ? error.message : error);
14492
+ console.error("[DASHBOARD] Failed to store monthly merged counts:", errorMessage(error));
14891
14493
  }
14892
14494
  try {
14893
14495
  stateManager2.setMonthlyClosedCounts(monthlyClosedCounts);
14894
14496
  } catch (error) {
14895
- console.error("[DASHBOARD] Failed to store monthly closed counts:", error instanceof Error ? error.message : error);
14497
+ console.error("[DASHBOARD] Failed to store monthly closed counts:", errorMessage(error));
14896
14498
  }
14897
14499
  try {
14898
14500
  const combinedOpenedCounts = { ...openedFromMerged };
@@ -14907,7 +14509,7 @@ async function fetchDashboardData(token) {
14907
14509
  }
14908
14510
  stateManager2.setMonthlyOpenedCounts(combinedOpenedCounts);
14909
14511
  } catch (error) {
14910
- console.error("[DASHBOARD] Failed to store monthly opened counts:", error instanceof Error ? error.message : error);
14512
+ console.error("[DASHBOARD] Failed to store monthly opened counts:", errorMessage(error));
14911
14513
  }
14912
14514
  const digest = prMonitor.generateDigest(prs, recentlyClosedPRs, recentlyMergedPRs);
14913
14515
  const shelvedUrls = new Set(stateManager2.getState().config.shelvedPRUrls || []);
@@ -14947,6 +14549,7 @@ var init_dashboard_data = __esm({
14947
14549
  "src/commands/dashboard-data.ts"() {
14948
14550
  "use strict";
14949
14551
  init_core();
14552
+ init_errors();
14950
14553
  init_daily();
14951
14554
  }
14952
14555
  });
@@ -16532,64 +16135,380 @@ var init_dashboard_templates = __esm({
16532
16135
  }
16533
16136
  });
16534
16137
 
16535
- // src/commands/dashboard.ts
16536
- var dashboard_exports = {};
16537
- __export(dashboard_exports, {
16538
- runDashboard: () => runDashboard,
16539
- writeDashboardFromState: () => writeDashboardFromState
16138
+ // src/commands/dashboard-server.ts
16139
+ var dashboard_server_exports = {};
16140
+ __export(dashboard_server_exports, {
16141
+ startDashboardServer: () => startDashboardServer
16540
16142
  });
16541
- async function runDashboard(options) {
16542
- const stateManager2 = getStateManager();
16543
- const token = options.offline ? null : getGitHubToken();
16544
- let digest;
16545
- let commentedIssues = [];
16546
- if (options.offline) {
16547
- const state2 = stateManager2.getState();
16548
- digest = state2.lastDigest;
16549
- if (!digest) {
16550
- if (options.json) {
16551
- outputJson({ error: "No cached data found. Run without --offline first.", offline: true });
16552
- } else {
16553
- console.error("No cached data found. Run without --offline first.");
16143
+ function buildDashboardJson(digest, state, commentedIssues) {
16144
+ const prsByRepo = computePRsByRepo(digest, state);
16145
+ const topRepos = computeTopRepos(prsByRepo);
16146
+ const { monthlyMerged } = getMonthlyData(state);
16147
+ const stats = buildDashboardStats(digest, state);
16148
+ const issueResponses = commentedIssues.filter((i) => i.status === "new_response");
16149
+ return {
16150
+ stats,
16151
+ prsByRepo,
16152
+ topRepos: topRepos.map(([repo, data]) => ({ repo, ...data })),
16153
+ monthlyMerged,
16154
+ activePRs: digest.openPRs || [],
16155
+ shelvedPRUrls: state.config.shelvedPRUrls || [],
16156
+ commentedIssues,
16157
+ issueResponses
16158
+ };
16159
+ }
16160
+ function readBody(req, maxBytes = MAX_BODY_BYTES) {
16161
+ return new Promise((resolve5, reject) => {
16162
+ const chunks = [];
16163
+ let totalLength = 0;
16164
+ let aborted = false;
16165
+ req.on("data", (chunk) => {
16166
+ if (aborted) return;
16167
+ totalLength += chunk.length;
16168
+ if (totalLength > maxBytes) {
16169
+ aborted = true;
16170
+ req.destroy();
16171
+ reject(new Error("Body too large"));
16172
+ return;
16554
16173
  }
16555
- return;
16556
- }
16557
- const lastUpdated = digest.generatedAt || state2.lastDigestAt || state2.lastRunAt;
16558
- console.error(`Offline mode: using cached data from ${lastUpdated}`);
16559
- } else if (token) {
16560
- console.error("Fetching fresh data from GitHub...");
16174
+ chunks.push(chunk);
16175
+ });
16176
+ req.on("end", () => {
16177
+ if (!aborted) resolve5(Buffer.concat(chunks).toString("utf-8"));
16178
+ });
16179
+ req.on("error", (err) => {
16180
+ if (!aborted) reject(err);
16181
+ });
16182
+ });
16183
+ }
16184
+ function sendJson(res, statusCode, data) {
16185
+ const body = JSON.stringify(data);
16186
+ res.writeHead(statusCode, {
16187
+ "Content-Type": "application/json",
16188
+ "Content-Length": Buffer.byteLength(body)
16189
+ });
16190
+ res.end(body);
16191
+ }
16192
+ function sendError(res, statusCode, message) {
16193
+ sendJson(res, statusCode, { error: message });
16194
+ }
16195
+ async function startDashboardServer(options) {
16196
+ const { port: requestedPort, assetsDir, token, open } = options;
16197
+ const stateManager2 = getStateManager();
16198
+ const resolvedAssetsDir = path5.resolve(assetsDir);
16199
+ let cachedDigest;
16200
+ let cachedCommentedIssues = [];
16201
+ if (token) {
16561
16202
  try {
16203
+ console.error("Fetching dashboard data from GitHub...");
16562
16204
  const result = await fetchDashboardData(token);
16563
- digest = result.digest;
16564
- commentedIssues = result.commentedIssues;
16205
+ cachedDigest = result.digest;
16206
+ cachedCommentedIssues = result.commentedIssues;
16565
16207
  } catch (error) {
16566
- console.error("Failed to fetch fresh data:", error instanceof Error ? error.message : error);
16567
- console.error("Falling back to cached data (issue conversations unavailable)...");
16568
- digest = stateManager2.getState().lastDigest;
16208
+ console.error("Failed to fetch data from GitHub:", error);
16209
+ console.error("Falling back to cached data...");
16210
+ cachedDigest = stateManager2.getState().lastDigest;
16569
16211
  }
16570
16212
  } else {
16571
- digest = stateManager2.getState().lastDigest;
16213
+ cachedDigest = stateManager2.getState().lastDigest;
16572
16214
  }
16573
- if (!digest) {
16574
- if (options.json) {
16575
- outputJson({ error: "No data available. Run daily check first with GITHUB_TOKEN." });
16576
- } else {
16577
- console.error("No dashboard data available. Run the daily check first:");
16578
- console.error(" GITHUB_TOKEN=$(gh auth token) npm start -- daily");
16579
- }
16580
- return;
16215
+ if (!cachedDigest) {
16216
+ console.error("No dashboard data available. Run the daily check first:");
16217
+ console.error(" GITHUB_TOKEN=$(gh auth token) npm start -- daily");
16218
+ process.exit(1);
16581
16219
  }
16582
- const state = stateManager2.getState();
16583
- const prsByRepo = computePRsByRepo(digest, state);
16584
- const topRepos = computeTopRepos(prsByRepo);
16585
- const { monthlyMerged, monthlyClosed, monthlyOpened } = getMonthlyData(state);
16586
- const stats = buildDashboardStats(digest, state);
16587
- if (options.json) {
16588
- const issueResponses2 = commentedIssues.filter((i) => i.status === "new_response");
16589
- const jsonData = {
16590
- stats,
16591
- prsByRepo,
16592
- topRepos: topRepos.map(([repo, data]) => ({ repo, ...data })),
16220
+ let cachedJsonData;
16221
+ try {
16222
+ cachedJsonData = buildDashboardJson(cachedDigest, stateManager2.getState(), cachedCommentedIssues);
16223
+ } catch (error) {
16224
+ console.error("Failed to build dashboard data from cached digest:", error);
16225
+ console.error("Your state data may be corrupted. Try running: daily --json");
16226
+ process.exit(1);
16227
+ }
16228
+ const server = http.createServer(async (req, res) => {
16229
+ const method = req.method || "GET";
16230
+ const url = req.url || "/";
16231
+ try {
16232
+ if (url === "/api/data" && method === "GET") {
16233
+ sendJson(res, 200, cachedJsonData);
16234
+ return;
16235
+ }
16236
+ if (url === "/api/action" && method === "POST") {
16237
+ await handleAction(req, res);
16238
+ return;
16239
+ }
16240
+ if (url === "/api/refresh" && method === "POST") {
16241
+ await handleRefresh(req, res);
16242
+ return;
16243
+ }
16244
+ if (method === "GET") {
16245
+ serveStaticFile(url, res);
16246
+ return;
16247
+ }
16248
+ sendError(res, 405, "Method not allowed");
16249
+ } catch (error) {
16250
+ console.error("Unhandled request error:", method, url, error);
16251
+ if (!res.headersSent) {
16252
+ sendError(res, 500, "Internal server error");
16253
+ }
16254
+ }
16255
+ });
16256
+ async function handleAction(req, res) {
16257
+ let body;
16258
+ try {
16259
+ const raw = await readBody(req);
16260
+ body = JSON.parse(raw);
16261
+ } catch (e) {
16262
+ const isBodyTooLarge = e instanceof Error && e.message === "Body too large";
16263
+ sendError(res, isBodyTooLarge ? 413 : 400, isBodyTooLarge ? "Request body too large" : "Invalid JSON body");
16264
+ return;
16265
+ }
16266
+ if (!body.action || !VALID_ACTIONS.has(body.action)) {
16267
+ sendError(res, 400, `Invalid action. Must be one of: ${[...VALID_ACTIONS].join(", ")}`);
16268
+ return;
16269
+ }
16270
+ if (!body.url || typeof body.url !== "string") {
16271
+ sendError(res, 400, 'Missing or invalid "url" field');
16272
+ return;
16273
+ }
16274
+ try {
16275
+ switch (body.action) {
16276
+ case "shelve":
16277
+ stateManager2.shelvePR(body.url);
16278
+ break;
16279
+ case "unshelve":
16280
+ stateManager2.unshelvePR(body.url);
16281
+ break;
16282
+ case "snooze":
16283
+ stateManager2.snoozePR(body.url, body.reason || "Snoozed via dashboard", body.days || 7);
16284
+ break;
16285
+ case "unsnooze":
16286
+ stateManager2.unsnoozePR(body.url);
16287
+ break;
16288
+ }
16289
+ stateManager2.save();
16290
+ } catch (error) {
16291
+ console.error("Action failed:", body.action, body.url, error);
16292
+ sendError(res, 500, `Action failed: ${errorMessage(error)}`);
16293
+ return;
16294
+ }
16295
+ if (cachedDigest) {
16296
+ cachedJsonData = buildDashboardJson(cachedDigest, stateManager2.getState(), cachedCommentedIssues);
16297
+ }
16298
+ sendJson(res, 200, cachedJsonData);
16299
+ }
16300
+ async function handleRefresh(_req, res) {
16301
+ const currentToken = token || getGitHubToken();
16302
+ if (!currentToken) {
16303
+ sendError(res, 401, "No GitHub token available. Cannot refresh data.");
16304
+ return;
16305
+ }
16306
+ try {
16307
+ console.error("Refreshing dashboard data from GitHub...");
16308
+ const result = await fetchDashboardData(currentToken);
16309
+ cachedDigest = result.digest;
16310
+ cachedCommentedIssues = result.commentedIssues;
16311
+ cachedJsonData = buildDashboardJson(cachedDigest, stateManager2.getState(), cachedCommentedIssues);
16312
+ sendJson(res, 200, cachedJsonData);
16313
+ } catch (error) {
16314
+ console.error("Dashboard refresh failed:", error);
16315
+ sendError(res, 500, `Refresh failed: ${errorMessage(error)}`);
16316
+ }
16317
+ }
16318
+ function serveStaticFile(requestUrl, res) {
16319
+ let urlPath;
16320
+ try {
16321
+ urlPath = decodeURIComponent(requestUrl.split("?")[0]);
16322
+ } catch (err) {
16323
+ console.error("Malformed URL received:", requestUrl, err);
16324
+ sendError(res, 400, "Malformed URL");
16325
+ return;
16326
+ }
16327
+ if (urlPath.includes("..")) {
16328
+ sendError(res, 403, "Forbidden");
16329
+ return;
16330
+ }
16331
+ const relativePath = urlPath === "/" ? "index.html" : urlPath.replace(/^\/+/, "");
16332
+ let filePath = path5.join(resolvedAssetsDir, relativePath);
16333
+ if (!filePath.startsWith(resolvedAssetsDir + path5.sep) && filePath !== resolvedAssetsDir) {
16334
+ sendError(res, 403, "Forbidden");
16335
+ return;
16336
+ }
16337
+ try {
16338
+ const stat = fs5.statSync(filePath);
16339
+ if (stat.isDirectory()) {
16340
+ filePath = path5.join(resolvedAssetsDir, "index.html");
16341
+ }
16342
+ } catch (err) {
16343
+ const nodeErr = err;
16344
+ if (nodeErr.code === "ENOENT") {
16345
+ filePath = path5.join(resolvedAssetsDir, "index.html");
16346
+ } else {
16347
+ console.error("Failed to stat file:", filePath, err);
16348
+ sendError(res, 500, "Internal server error");
16349
+ return;
16350
+ }
16351
+ }
16352
+ const ext = path5.extname(filePath).toLowerCase();
16353
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
16354
+ try {
16355
+ const content = fs5.readFileSync(filePath);
16356
+ res.writeHead(200, {
16357
+ "Content-Type": contentType,
16358
+ "Content-Length": content.length
16359
+ });
16360
+ res.end(content);
16361
+ } catch (error) {
16362
+ const nodeErr = error;
16363
+ if (nodeErr.code === "ENOENT") {
16364
+ sendError(res, 404, "Not found");
16365
+ } else {
16366
+ console.error("Failed to serve static file:", filePath, error);
16367
+ sendError(res, 500, "Failed to read file");
16368
+ }
16369
+ }
16370
+ }
16371
+ const MAX_PORT_ATTEMPTS = 10;
16372
+ let actualPort = requestedPort;
16373
+ for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
16374
+ try {
16375
+ await new Promise((resolve5, reject) => {
16376
+ server.once("error", reject);
16377
+ server.listen(actualPort, "127.0.0.1", () => resolve5());
16378
+ });
16379
+ break;
16380
+ } catch (err) {
16381
+ const nodeErr = err;
16382
+ if (nodeErr.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS - 1) {
16383
+ console.error(`Port ${actualPort} is in use, trying ${actualPort + 1}...`);
16384
+ actualPort++;
16385
+ continue;
16386
+ }
16387
+ console.error(`Failed to start server: ${nodeErr.message}`);
16388
+ process.exit(1);
16389
+ }
16390
+ }
16391
+ const serverUrl = `http://localhost:${actualPort}`;
16392
+ console.error(`Dashboard server running at ${serverUrl}`);
16393
+ if (open) {
16394
+ const { execFile: execFile4 } = await import("child_process");
16395
+ let openCmd;
16396
+ let args;
16397
+ switch (process.platform) {
16398
+ case "darwin":
16399
+ openCmd = "open";
16400
+ args = [serverUrl];
16401
+ break;
16402
+ case "win32":
16403
+ openCmd = "cmd";
16404
+ args = ["/c", "start", "", serverUrl];
16405
+ break;
16406
+ default:
16407
+ openCmd = "xdg-open";
16408
+ args = [serverUrl];
16409
+ break;
16410
+ }
16411
+ execFile4(openCmd, args, (error) => {
16412
+ if (error) {
16413
+ console.error("Failed to open browser:", error.message);
16414
+ console.error(`Open manually: ${serverUrl}`);
16415
+ }
16416
+ });
16417
+ }
16418
+ const shutdown = () => {
16419
+ console.error("\nShutting down dashboard server...");
16420
+ server.close(() => {
16421
+ process.exit(0);
16422
+ });
16423
+ setTimeout(() => process.exit(0), 3e3).unref();
16424
+ };
16425
+ process.on("SIGINT", shutdown);
16426
+ process.on("SIGTERM", shutdown);
16427
+ }
16428
+ var http, fs5, path5, VALID_ACTIONS, MAX_BODY_BYTES, MIME_TYPES;
16429
+ var init_dashboard_server = __esm({
16430
+ "src/commands/dashboard-server.ts"() {
16431
+ "use strict";
16432
+ http = __toESM(require("http"), 1);
16433
+ fs5 = __toESM(require("fs"), 1);
16434
+ path5 = __toESM(require("path"), 1);
16435
+ init_core();
16436
+ init_errors();
16437
+ init_dashboard_data();
16438
+ init_dashboard_templates();
16439
+ VALID_ACTIONS = /* @__PURE__ */ new Set(["shelve", "unshelve", "snooze", "unsnooze"]);
16440
+ MAX_BODY_BYTES = 10240;
16441
+ MIME_TYPES = {
16442
+ ".html": "text/html",
16443
+ ".js": "application/javascript",
16444
+ ".css": "text/css",
16445
+ ".svg": "image/svg+xml",
16446
+ ".json": "application/json",
16447
+ ".png": "image/png",
16448
+ ".ico": "image/x-icon"
16449
+ };
16450
+ }
16451
+ });
16452
+
16453
+ // src/commands/dashboard.ts
16454
+ var dashboard_exports = {};
16455
+ __export(dashboard_exports, {
16456
+ runDashboard: () => runDashboard,
16457
+ serveDashboard: () => serveDashboard,
16458
+ writeDashboardFromState: () => writeDashboardFromState
16459
+ });
16460
+ async function runDashboard(options) {
16461
+ const stateManager2 = getStateManager();
16462
+ const token = options.offline ? null : getGitHubToken();
16463
+ let digest;
16464
+ let commentedIssues = [];
16465
+ if (options.offline) {
16466
+ const state2 = stateManager2.getState();
16467
+ digest = state2.lastDigest;
16468
+ if (!digest) {
16469
+ if (options.json) {
16470
+ outputJson({ error: "No cached data found. Run without --offline first.", offline: true });
16471
+ } else {
16472
+ console.error("No cached data found. Run without --offline first.");
16473
+ }
16474
+ return;
16475
+ }
16476
+ const lastUpdated = digest.generatedAt || state2.lastDigestAt || state2.lastRunAt;
16477
+ console.error(`Offline mode: using cached data from ${lastUpdated}`);
16478
+ } else if (token) {
16479
+ console.error("Fetching fresh data from GitHub...");
16480
+ try {
16481
+ const result = await fetchDashboardData(token);
16482
+ digest = result.digest;
16483
+ commentedIssues = result.commentedIssues;
16484
+ } catch (error) {
16485
+ console.error("Failed to fetch fresh data:", errorMessage(error));
16486
+ console.error("Falling back to cached data (issue conversations unavailable)...");
16487
+ digest = stateManager2.getState().lastDigest;
16488
+ }
16489
+ } else {
16490
+ digest = stateManager2.getState().lastDigest;
16491
+ }
16492
+ if (!digest) {
16493
+ if (options.json) {
16494
+ outputJson({ error: "No data available. Run daily check first with GITHUB_TOKEN." });
16495
+ } else {
16496
+ console.error("No dashboard data available. Run the daily check first:");
16497
+ console.error(" GITHUB_TOKEN=$(gh auth token) npm start -- daily");
16498
+ }
16499
+ return;
16500
+ }
16501
+ const state = stateManager2.getState();
16502
+ const prsByRepo = computePRsByRepo(digest, state);
16503
+ const topRepos = computeTopRepos(prsByRepo);
16504
+ const { monthlyMerged, monthlyClosed, monthlyOpened } = getMonthlyData(state);
16505
+ const stats = buildDashboardStats(digest, state);
16506
+ if (options.json) {
16507
+ const issueResponses2 = commentedIssues.filter((i) => i.status === "new_response");
16508
+ const jsonData = {
16509
+ stats,
16510
+ prsByRepo,
16511
+ topRepos: topRepos.map(([repo, data]) => ({ repo, ...data })),
16593
16512
  monthlyMerged,
16594
16513
  activePRs: digest.openPRs || [],
16595
16514
  commentedIssues,
@@ -16605,8 +16524,8 @@ async function runDashboard(options) {
16605
16524
  const issueResponses = commentedIssues.filter((i) => i.status === "new_response");
16606
16525
  const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses);
16607
16526
  const dashboardPath = getDashboardPath();
16608
- fs5.writeFileSync(dashboardPath, html, { mode: 420 });
16609
- fs5.chmodSync(dashboardPath, 420);
16527
+ fs6.writeFileSync(dashboardPath, html, { mode: 420 });
16528
+ fs6.chmodSync(dashboardPath, 420);
16610
16529
  if (options.offline) {
16611
16530
  const lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
16612
16531
  console.log(`
@@ -16641,17 +16560,59 @@ function writeDashboardFromState() {
16641
16560
  const stats = buildDashboardStats(digest, state);
16642
16561
  const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state);
16643
16562
  const dashboardPath = getDashboardPath();
16644
- fs5.writeFileSync(dashboardPath, html, { mode: 420 });
16645
- fs5.chmodSync(dashboardPath, 420);
16563
+ fs6.writeFileSync(dashboardPath, html, { mode: 420 });
16564
+ fs6.chmodSync(dashboardPath, 420);
16646
16565
  return dashboardPath;
16647
16566
  }
16648
- var fs5, import_child_process2;
16567
+ function resolveAssetsDir() {
16568
+ const devPath = path6.resolve(__dirname, "../../dashboard/dist");
16569
+ if (fs6.existsSync(path6.join(devPath, "index.html"))) {
16570
+ return devPath;
16571
+ }
16572
+ const bundlePath = path6.resolve(path6.dirname(process.argv[1]), "../../dashboard/dist");
16573
+ if (fs6.existsSync(path6.join(bundlePath, "index.html"))) {
16574
+ return bundlePath;
16575
+ }
16576
+ try {
16577
+ const dashboardPkgPath = require.resolve("@oss-autopilot/dashboard/package.json");
16578
+ const dashboardDist = path6.join(path6.dirname(dashboardPkgPath), "dist");
16579
+ if (fs6.existsSync(path6.join(dashboardDist, "index.html"))) {
16580
+ return dashboardDist;
16581
+ }
16582
+ } catch (error) {
16583
+ const code = error.code;
16584
+ if (code !== "MODULE_NOT_FOUND") {
16585
+ console.error("Error resolving dashboard package:", error);
16586
+ }
16587
+ }
16588
+ return null;
16589
+ }
16590
+ async function serveDashboard(options) {
16591
+ const assetsDir = resolveAssetsDir();
16592
+ if (!assetsDir) {
16593
+ console.error("Could not find dashboard SPA assets.");
16594
+ console.error("Make sure packages/dashboard has been built:");
16595
+ console.error(" cd packages/dashboard && pnpm run build");
16596
+ process.exit(1);
16597
+ }
16598
+ const token = getGitHubToken();
16599
+ const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_dashboard_server(), dashboard_server_exports));
16600
+ await startDashboardServer2({
16601
+ port: options.port,
16602
+ assetsDir,
16603
+ token,
16604
+ open: options.open
16605
+ });
16606
+ }
16607
+ var fs6, path6, import_child_process2;
16649
16608
  var init_dashboard = __esm({
16650
16609
  "src/commands/dashboard.ts"() {
16651
16610
  "use strict";
16652
- fs5 = __toESM(require("fs"), 1);
16611
+ fs6 = __toESM(require("fs"), 1);
16612
+ path6 = __toESM(require("path"), 1);
16653
16613
  import_child_process2 = require("child_process");
16654
16614
  init_core();
16615
+ init_errors();
16655
16616
  init_json();
16656
16617
  init_dashboard_data();
16657
16618
  init_dashboard_templates();
@@ -16682,7 +16643,7 @@ function extractTitle(line) {
16682
16643
  cleaned = cleaned.replace(/\[[ xX]\]\s*/, "");
16683
16644
  cleaned = cleaned.replace(/~~/g, "");
16684
16645
  cleaned = cleaned.replace(/\b(Done|DONE|done)\b/g, "");
16685
- cleaned = cleaned.replace(/^[\s\-–—:]+/, "").replace(/[\s\-–—:]+$/, "");
16646
+ cleaned = cleaned.replace(/^[\s\-\u2013\u2014:]+/, "").replace(/[\s\-\u2013\u2014:]+$/, "");
16686
16647
  return cleaned.trim();
16687
16648
  }
16688
16649
  function isCompleted(line) {
@@ -16729,57 +16690,26 @@ function parseIssueList(content) {
16729
16690
  };
16730
16691
  }
16731
16692
  async function runParseList(options) {
16732
- const filePath = path5.resolve(options.filePath);
16733
- if (!fs6.existsSync(filePath)) {
16734
- if (options.json) {
16735
- outputJsonError(`File not found: ${filePath}`);
16736
- } else {
16737
- console.error(`Error: File not found: ${filePath}`);
16738
- }
16739
- process.exit(1);
16693
+ const filePath = path7.resolve(options.filePath);
16694
+ if (!fs7.existsSync(filePath)) {
16695
+ throw new Error(`File not found: ${filePath}`);
16740
16696
  }
16741
16697
  let content;
16742
16698
  try {
16743
- content = fs6.readFileSync(filePath, "utf-8");
16699
+ content = fs7.readFileSync(filePath, "utf-8");
16744
16700
  } catch (error) {
16745
- const msg = error instanceof Error ? error.message : String(error);
16746
- if (options.json) {
16747
- outputJsonError(`Failed to read file: ${msg}`);
16748
- } else {
16749
- console.error(`Error: Failed to read file: ${msg}`);
16750
- }
16751
- process.exit(1);
16752
- }
16753
- const result = parseIssueList(content);
16754
- if (options.json) {
16755
- outputJson(result);
16756
- } else {
16757
- console.log(`
16758
- \u{1F4CB} Issue List: ${filePath}
16759
- `);
16760
- console.log(`Available: ${result.availableCount} | Completed: ${result.completedCount}
16761
- `);
16762
- if (result.available.length > 0) {
16763
- console.log("--- Available ---");
16764
- for (const item of result.available) {
16765
- console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
16766
- }
16767
- }
16768
- if (result.completed.length > 0) {
16769
- console.log("\n--- Completed ---");
16770
- for (const item of result.completed) {
16771
- console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
16772
- }
16773
- }
16701
+ const msg = errorMessage(error);
16702
+ throw new Error(`Failed to read file: ${msg}`, { cause: error });
16774
16703
  }
16704
+ return parseIssueList(content);
16775
16705
  }
16776
- var fs6, path5;
16706
+ var fs7, path7;
16777
16707
  var init_parse_list = __esm({
16778
16708
  "src/commands/parse-list.ts"() {
16779
16709
  "use strict";
16780
- fs6 = __toESM(require("fs"), 1);
16781
- path5 = __toESM(require("path"), 1);
16782
- init_json();
16710
+ fs7 = __toESM(require("fs"), 1);
16711
+ path7 = __toESM(require("path"), 1);
16712
+ init_errors();
16783
16713
  }
16784
16714
  });
16785
16715
 
@@ -16789,25 +16719,25 @@ __export(check_integration_exports, {
16789
16719
  runCheckIntegration: () => runCheckIntegration
16790
16720
  });
16791
16721
  function getImportName(filePath) {
16792
- const ext = path6.extname(filePath);
16793
- const base = path6.basename(filePath, ext);
16722
+ const ext = path8.extname(filePath);
16723
+ const base = path8.basename(filePath, ext);
16794
16724
  if (base === "index") {
16795
- return path6.basename(path6.dirname(filePath));
16725
+ return path8.basename(path8.dirname(filePath));
16796
16726
  }
16797
16727
  return base;
16798
16728
  }
16799
16729
  function suggestEntryPoints(newFile, existingFiles) {
16800
- const dir = path6.dirname(newFile);
16730
+ const dir = path8.dirname(newFile);
16801
16731
  const suggestions = [];
16802
16732
  for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
16803
- const indexFile = path6.join(dir, `index${ext}`);
16733
+ const indexFile = path8.join(dir, `index${ext}`);
16804
16734
  if (existingFiles.includes(indexFile)) {
16805
16735
  suggestions.push(indexFile);
16806
16736
  }
16807
16737
  }
16808
- const parentDir = path6.dirname(dir);
16738
+ const parentDir = path8.dirname(dir);
16809
16739
  for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
16810
- const parentIndex = path6.join(parentDir, `index${ext}`);
16740
+ const parentIndex = path8.join(parentDir, `index${ext}`);
16811
16741
  if (existingFiles.includes(parentIndex)) {
16812
16742
  suggestions.push(parentIndex);
16813
16743
  }
@@ -16818,33 +16748,22 @@ async function runCheckIntegration(options) {
16818
16748
  const base = options.base;
16819
16749
  let newFiles;
16820
16750
  try {
16821
- const output2 = (0, import_child_process3.execFileSync)("git", ["diff", "--name-only", "--diff-filter=A", `${base}...HEAD`], {
16751
+ const output = (0, import_child_process3.execFileSync)("git", ["diff", "--name-only", "--diff-filter=A", `${base}...HEAD`], {
16822
16752
  encoding: "utf-8",
16823
16753
  timeout: 1e4
16824
16754
  }).trim();
16825
- newFiles = output2 ? output2.split("\n").filter(Boolean) : [];
16755
+ newFiles = output ? output.split("\n").filter(Boolean) : [];
16826
16756
  } catch (error) {
16827
- const msg = error instanceof Error ? error.message : String(error);
16828
- if (options.json) {
16829
- outputJsonError(`Failed to run git diff: ${msg}`);
16830
- } else {
16831
- console.error(`Error: Failed to run git diff: ${msg}`);
16832
- }
16833
- process.exit(1);
16757
+ const msg = errorMessage(error);
16758
+ throw new Error(`Failed to run git diff: ${msg}`, { cause: error });
16834
16759
  }
16835
16760
  const codeFiles = newFiles.filter((f) => {
16836
- const ext = path6.extname(f);
16761
+ const ext = path8.extname(f);
16837
16762
  if (!CODE_EXTENSIONS.has(ext)) return false;
16838
16763
  return !IGNORED_PATTERNS.some((p) => p.test(f));
16839
16764
  });
16840
16765
  if (codeFiles.length === 0) {
16841
- const result = { newFiles: [], unreferencedCount: 0 };
16842
- if (options.json) {
16843
- outputJson(result);
16844
- } else {
16845
- console.log("\nNo new code files to check.");
16846
- }
16847
- return;
16766
+ return { newFiles: [], unreferencedCount: 0 };
16848
16767
  }
16849
16768
  let allFiles;
16850
16769
  try {
@@ -16879,10 +16798,10 @@ async function runCheckIntegration(options) {
16879
16798
  referencedBy.push(...matches);
16880
16799
  }
16881
16800
  } catch (error) {
16882
- const exitCode = error && typeof error === "object" && "status" in error ? error.status : null;
16883
- if (exitCode !== null && exitCode !== 1) {
16884
- const msg = error instanceof Error ? error.message : String(error);
16885
- console.error(`Warning: git grep failed for "${pattern}": ${msg}`);
16801
+ const exitCode = error && typeof error === "object" && "status" in error ? error.status : void 0;
16802
+ if (exitCode !== void 0 && exitCode !== 1) {
16803
+ const msg = errorMessage(error);
16804
+ debug("check-integration", `git grep failed for "${pattern}": ${msg}`);
16886
16805
  }
16887
16806
  }
16888
16807
  }
@@ -16899,37 +16818,16 @@ async function runCheckIntegration(options) {
16899
16818
  results.push(info);
16900
16819
  }
16901
16820
  const unreferencedCount = results.filter((r) => !r.isIntegrated).length;
16902
- const output = { newFiles: results, unreferencedCount };
16903
- if (options.json) {
16904
- outputJson(output);
16905
- } else {
16906
- console.log(`
16907
- \u{1F50D} Integration Check (base: ${base})
16908
- `);
16909
- console.log(`New files: ${results.length} | Unreferenced: ${unreferencedCount}
16910
- `);
16911
- for (const file of results) {
16912
- const status = file.isIntegrated ? "\u2705" : "\u26A0\uFE0F";
16913
- console.log(`${status} ${file.path}`);
16914
- if (file.isIntegrated) {
16915
- console.log(` Referenced by: ${file.referencedBy.join(", ")}`);
16916
- } else {
16917
- console.log(" Not referenced by any file");
16918
- if (file.suggestedEntryPoints && file.suggestedEntryPoints.length > 0) {
16919
- console.log(` Suggested entry points: ${file.suggestedEntryPoints.join(", ")}`);
16920
- }
16921
- }
16922
- }
16923
- }
16821
+ return { newFiles: results, unreferencedCount };
16924
16822
  }
16925
- var path6, import_child_process3, CODE_EXTENSIONS, IGNORED_PATTERNS;
16823
+ var path8, import_child_process3, CODE_EXTENSIONS, IGNORED_PATTERNS;
16926
16824
  var init_check_integration = __esm({
16927
16825
  "src/commands/check-integration.ts"() {
16928
16826
  "use strict";
16929
- path6 = __toESM(require("path"), 1);
16827
+ path8 = __toESM(require("path"), 1);
16930
16828
  import_child_process3 = require("child_process");
16931
- init_json();
16932
16829
  init_core();
16830
+ init_errors();
16933
16831
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
16934
16832
  ".ts",
16935
16833
  ".tsx",
@@ -17005,7 +16903,7 @@ function getCurrentBranch(repoPath) {
17005
16903
  function scanForRepos(scanPaths) {
17006
16904
  const repos = {};
17007
16905
  for (const scanPath of scanPaths) {
17008
- if (!fs7.existsSync(scanPath)) continue;
16906
+ if (!fs8.existsSync(scanPath)) continue;
17009
16907
  let gitDirs;
17010
16908
  try {
17011
16909
  const output = (0, import_child_process4.execFileSync)("find", [scanPath, "-maxdepth", "4", "-name", ".git", "-type", "d"], {
@@ -17019,7 +16917,7 @@ function scanForRepos(scanPaths) {
17019
16917
  continue;
17020
16918
  }
17021
16919
  for (const gitDir of gitDirs) {
17022
- const repoPath = path7.dirname(gitDir);
16920
+ const repoPath = path9.dirname(gitDir);
17023
16921
  const remote = getGitHubRemote(repoPath);
17024
16922
  if (!remote) continue;
17025
16923
  const currentBranch = getCurrentBranch(repoPath);
@@ -17035,79 +16933,49 @@ function scanForRepos(scanPaths) {
17035
16933
  async function runLocalRepos(options) {
17036
16934
  const stateManager2 = getStateManager();
17037
16935
  const state = stateManager2.getState();
17038
- const scanPaths = options.paths?.map((p) => path7.resolve(p)) ?? state.config.localRepoScanPaths ?? DEFAULT_SCAN_PATHS.filter((p) => fs7.existsSync(p));
16936
+ const scanPaths = options.paths?.map((p) => path9.resolve(p)) ?? state.config.localRepoScanPaths ?? DEFAULT_SCAN_PATHS.filter((p) => fs8.existsSync(p));
17039
16937
  if (!options.scan && state.localRepoCache) {
17040
16938
  const cache = state.localRepoCache;
17041
- const result2 = {
16939
+ return {
17042
16940
  repos: cache.repos,
17043
16941
  scanPaths: cache.scanPaths,
17044
16942
  cachedAt: cache.cachedAt,
17045
16943
  fromCache: true
17046
16944
  };
17047
- if (options.json) {
17048
- outputJson(result2);
17049
- } else {
17050
- console.log(`
17051
- \u{1F4C1} Local Repos (cached ${cache.cachedAt})
17052
- `);
17053
- printRepos(cache.repos);
17054
- }
17055
- return;
17056
- }
17057
- if (!options.json) {
17058
- console.log(`
17059
- \u{1F50D} Scanning for local repos in ${scanPaths.length} directories...
17060
- `);
17061
16945
  }
17062
16946
  const repos = scanForRepos(scanPaths);
17063
- const repoCount = Object.keys(repos).length;
17064
16947
  const cachedAt = (/* @__PURE__ */ new Date()).toISOString();
17065
16948
  try {
17066
16949
  stateManager2.setLocalRepoCache({ repos, scanPaths, cachedAt });
17067
16950
  stateManager2.save();
17068
16951
  } catch (error) {
17069
- const msg = error instanceof Error ? error.message : String(error);
17070
- console.error(`Warning: Failed to cache scan results: ${msg}`);
16952
+ const msg = errorMessage(error);
16953
+ debug("local-repos", `Failed to cache scan results: ${msg}`);
17071
16954
  }
17072
- const result = {
16955
+ return {
17073
16956
  repos,
17074
16957
  scanPaths,
17075
16958
  cachedAt,
17076
16959
  fromCache: false
17077
16960
  };
17078
- if (options.json) {
17079
- outputJson(result);
17080
- } else {
17081
- console.log(`Found ${repoCount} repos:
17082
- `);
17083
- printRepos(repos);
17084
- }
17085
- }
17086
- function printRepos(repos) {
17087
- const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
17088
- for (const [remote, info] of entries) {
17089
- const branch = info.currentBranch ? ` (${info.currentBranch})` : "";
17090
- console.log(` ${remote}${branch}`);
17091
- console.log(` ${info.path}`);
17092
- }
17093
16961
  }
17094
- var fs7, path7, os2, import_child_process4, DEFAULT_SCAN_PATHS;
16962
+ var fs8, path9, os2, import_child_process4, DEFAULT_SCAN_PATHS;
17095
16963
  var init_local_repos = __esm({
17096
16964
  "src/commands/local-repos.ts"() {
17097
16965
  "use strict";
17098
- fs7 = __toESM(require("fs"), 1);
17099
- path7 = __toESM(require("path"), 1);
16966
+ fs8 = __toESM(require("fs"), 1);
16967
+ path9 = __toESM(require("path"), 1);
17100
16968
  os2 = __toESM(require("os"), 1);
17101
16969
  import_child_process4 = require("child_process");
17102
16970
  init_core();
17103
- init_json();
16971
+ init_errors();
17104
16972
  DEFAULT_SCAN_PATHS = [
17105
- path7.join(os2.homedir(), "Documents", "oss"),
17106
- path7.join(os2.homedir(), "dev"),
17107
- path7.join(os2.homedir(), "projects"),
17108
- path7.join(os2.homedir(), "src"),
17109
- path7.join(os2.homedir(), "code"),
17110
- path7.join(os2.homedir(), "repos")
16973
+ path9.join(os2.homedir(), "Documents", "oss"),
16974
+ path9.join(os2.homedir(), "dev"),
16975
+ path9.join(os2.homedir(), "projects"),
16976
+ path9.join(os2.homedir(), "src"),
16977
+ path9.join(os2.homedir(), "code"),
16978
+ path9.join(os2.homedir(), "repos")
17111
16979
  ];
17112
16980
  }
17113
16981
  });
@@ -17120,15 +16988,6 @@ __export(startup_exports, {
17120
16988
  parseIssueListPathFromConfig: () => parseIssueListPathFromConfig,
17121
16989
  runStartup: () => runStartup
17122
16990
  });
17123
- function getVersion() {
17124
- try {
17125
- const pkgPath = path8.join(path8.dirname(process.argv[1]), "..", "package.json");
17126
- return JSON.parse(fs8.readFileSync(pkgPath, "utf-8")).version;
17127
- } catch (error) {
17128
- console.error("[STARTUP] Failed to detect CLI version:", error instanceof Error ? error.message : error);
17129
- return "0.0.0";
17130
- }
17131
- }
17132
16991
  function parseIssueListPathFromConfig(configContent) {
17133
16992
  const match = configContent.match(/^---\n([\s\S]*?)\n---/);
17134
16993
  if (!match) return void 0;
@@ -17155,22 +17014,22 @@ function detectIssueList() {
17155
17014
  let issueListPath = "";
17156
17015
  let source = "auto-detected";
17157
17016
  const configPath = ".claude/oss-autopilot/config.md";
17158
- if (fs8.existsSync(configPath)) {
17017
+ if (fs9.existsSync(configPath)) {
17159
17018
  try {
17160
- const configContent = fs8.readFileSync(configPath, "utf-8");
17019
+ const configContent = fs9.readFileSync(configPath, "utf-8");
17161
17020
  const configuredPath = parseIssueListPathFromConfig(configContent);
17162
- if (configuredPath && fs8.existsSync(configuredPath)) {
17021
+ if (configuredPath && fs9.existsSync(configuredPath)) {
17163
17022
  issueListPath = configuredPath;
17164
17023
  source = "configured";
17165
17024
  }
17166
17025
  } catch (error) {
17167
- console.error("[STARTUP] Failed to read config:", error instanceof Error ? error.message : error);
17026
+ console.error("[STARTUP] Failed to read config:", errorMessage(error));
17168
17027
  }
17169
17028
  }
17170
17029
  if (!issueListPath) {
17171
17030
  const probes = ["open-source/potential-issue-list.md", "oss/issue-list.md", "issues.md"];
17172
17031
  for (const probe of probes) {
17173
- if (fs8.existsSync(probe)) {
17032
+ if (fs9.existsSync(probe)) {
17174
17033
  issueListPath = probe;
17175
17034
  source = "auto-detected";
17176
17035
  break;
@@ -17179,14 +17038,11 @@ function detectIssueList() {
17179
17038
  }
17180
17039
  if (!issueListPath) return void 0;
17181
17040
  try {
17182
- const content = fs8.readFileSync(issueListPath, "utf-8");
17041
+ const content = fs9.readFileSync(issueListPath, "utf-8");
17183
17042
  const { availableCount, completedCount } = countIssueListItems(content);
17184
17043
  return { path: issueListPath, source, availableCount, completedCount };
17185
17044
  } catch (error) {
17186
- console.error(
17187
- `[STARTUP] Failed to read issue list at ${issueListPath}:`,
17188
- error instanceof Error ? error.message : error
17189
- );
17045
+ console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, errorMessage(error));
17190
17046
  return { path: issueListPath, source, availableCount: 0, completedCount: 0 };
17191
17047
  }
17192
17048
  }
@@ -17200,82 +17056,52 @@ function openInBrowser(filePath) {
17200
17056
  }
17201
17057
  });
17202
17058
  }
17203
- async function runStartup(options) {
17204
- const version = getVersion();
17059
+ async function runStartup() {
17060
+ const version = getCLIVersion();
17205
17061
  const stateManager2 = getStateManager();
17206
17062
  if (!stateManager2.isSetupComplete()) {
17207
- if (options.json) {
17208
- outputJson({ version, setupComplete: false });
17209
- } else {
17210
- console.log("Setup incomplete. Run /setup-oss first.");
17211
- }
17212
- return;
17063
+ return { version, setupComplete: false };
17213
17064
  }
17214
17065
  const token = getGitHubToken();
17215
17066
  if (!token) {
17216
- if (options.json) {
17217
- outputJson({
17218
- version,
17219
- setupComplete: true,
17220
- authError: 'GitHub authentication required. Install GitHub CLI (https://cli.github.com/) and run "gh auth login", or set GITHUB_TOKEN.'
17221
- });
17222
- } else {
17223
- console.error("Error: GitHub authentication required.");
17224
- }
17225
- return;
17067
+ return {
17068
+ version,
17069
+ setupComplete: true,
17070
+ authError: 'GitHub authentication required. Install GitHub CLI (https://cli.github.com/) and run "gh auth login", or set GITHUB_TOKEN.'
17071
+ };
17226
17072
  }
17073
+ const daily = await executeDailyCheck(token);
17074
+ let dashboardPath;
17075
+ let dashboardOpened = false;
17227
17076
  try {
17228
- const daily = await executeDailyCheck(token);
17229
- let dashboardPath;
17230
- let dashboardOpened = false;
17231
- try {
17232
- dashboardPath = writeDashboardFromState();
17233
- if (daily.digest.summary.totalActivePRs > 0) {
17234
- openInBrowser(dashboardPath);
17235
- dashboardOpened = true;
17236
- }
17237
- } catch (error) {
17238
- console.error("[STARTUP] Dashboard generation failed:", error instanceof Error ? error.message : error);
17239
- }
17240
- if (dashboardOpened) {
17241
- daily.briefSummary += " | Dashboard opened in browser";
17242
- }
17243
- const issueList = detectIssueList();
17244
- if (options.json) {
17245
- outputJson({
17246
- version,
17247
- setupComplete: true,
17248
- daily,
17249
- dashboardPath,
17250
- issueList
17251
- });
17252
- } else {
17253
- console.log(`OSS Autopilot v${version}`);
17254
- console.log(daily.briefSummary);
17255
- if (dashboardPath) console.log(`Dashboard: ${dashboardPath}`);
17077
+ dashboardPath = writeDashboardFromState();
17078
+ if (daily.digest.summary.totalActivePRs > 0) {
17079
+ openInBrowser(dashboardPath);
17080
+ dashboardOpened = true;
17256
17081
  }
17257
17082
  } catch (error) {
17258
- const msg = error instanceof Error ? error.message : String(error);
17259
- if (options.json) {
17260
- outputJsonError(`Daily check failed: ${msg}`);
17261
- } else {
17262
- console.error(`[FATAL] Daily check failed: ${msg}`);
17263
- if (error instanceof Error && error.stack) {
17264
- console.error(error.stack);
17265
- }
17266
- }
17267
- process.exit(1);
17083
+ console.error("[STARTUP] Dashboard generation failed:", errorMessage(error));
17268
17084
  }
17085
+ if (dashboardOpened) {
17086
+ daily.briefSummary += " | Dashboard opened in browser";
17087
+ }
17088
+ const issueList = detectIssueList();
17089
+ return {
17090
+ version,
17091
+ setupComplete: true,
17092
+ daily,
17093
+ dashboardPath,
17094
+ issueList
17095
+ };
17269
17096
  }
17270
- var fs8, path8, import_child_process5;
17097
+ var fs9, import_child_process5;
17271
17098
  var init_startup = __esm({
17272
17099
  "src/commands/startup.ts"() {
17273
17100
  "use strict";
17274
- fs8 = __toESM(require("fs"), 1);
17275
- path8 = __toESM(require("path"), 1);
17101
+ fs9 = __toESM(require("fs"), 1);
17276
17102
  import_child_process5 = require("child_process");
17277
17103
  init_core();
17278
- init_json();
17104
+ init_errors();
17279
17105
  init_daily();
17280
17106
  init_dashboard();
17281
17107
  }
@@ -17290,44 +17116,28 @@ __export(shelve_exports, {
17290
17116
  });
17291
17117
  async function runShelve(options) {
17292
17118
  validateUrl(options.prUrl);
17293
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
17119
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
17294
17120
  const stateManager2 = getStateManager();
17295
17121
  const added = stateManager2.shelvePR(options.prUrl);
17296
17122
  if (added) {
17297
17123
  stateManager2.save();
17298
17124
  }
17299
- if (options.json) {
17300
- outputJson({ shelved: added, url: options.prUrl });
17301
- } else if (added) {
17302
- console.log(`Shelved: ${options.prUrl}`);
17303
- console.log("This PR is now excluded from capacity and actionable issues.");
17304
- console.log("It will auto-unshelve if a maintainer engages.");
17305
- } else {
17306
- console.log("PR is already shelved.");
17307
- }
17125
+ return { shelved: added, url: options.prUrl };
17308
17126
  }
17309
17127
  async function runUnshelve(options) {
17310
17128
  validateUrl(options.prUrl);
17311
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
17129
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
17312
17130
  const stateManager2 = getStateManager();
17313
17131
  const removed = stateManager2.unshelvePR(options.prUrl);
17314
17132
  if (removed) {
17315
17133
  stateManager2.save();
17316
17134
  }
17317
- if (options.json) {
17318
- outputJson({ unshelved: removed, url: options.prUrl });
17319
- } else if (removed) {
17320
- console.log(`Unshelved: ${options.prUrl}`);
17321
- console.log("This PR is now active again.");
17322
- } else {
17323
- console.log("PR was not shelved.");
17324
- }
17135
+ return { unshelved: removed, url: options.prUrl };
17325
17136
  }
17326
17137
  var init_shelve = __esm({
17327
17138
  "src/commands/shelve.ts"() {
17328
17139
  "use strict";
17329
17140
  init_core();
17330
- init_json();
17331
17141
  init_validation();
17332
17142
  }
17333
17143
  });
@@ -17341,44 +17151,28 @@ __export(dismiss_exports, {
17341
17151
  });
17342
17152
  async function runDismiss(options) {
17343
17153
  validateUrl(options.issueUrl);
17344
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue", options.json);
17154
+ validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
17345
17155
  const stateManager2 = getStateManager();
17346
17156
  const added = stateManager2.dismissIssue(options.issueUrl, (/* @__PURE__ */ new Date()).toISOString());
17347
17157
  if (added) {
17348
17158
  stateManager2.save();
17349
17159
  }
17350
- if (options.json) {
17351
- outputJson({ dismissed: added, url: options.issueUrl });
17352
- } else if (added) {
17353
- console.log(`Dismissed: ${options.issueUrl}`);
17354
- console.log("Issue reply notifications are now muted.");
17355
- console.log("New responses after this point will resurface automatically.");
17356
- } else {
17357
- console.log("Issue is already dismissed.");
17358
- }
17160
+ return { dismissed: added, url: options.issueUrl };
17359
17161
  }
17360
17162
  async function runUndismiss(options) {
17361
17163
  validateUrl(options.issueUrl);
17362
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue", options.json);
17164
+ validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
17363
17165
  const stateManager2 = getStateManager();
17364
17166
  const removed = stateManager2.undismissIssue(options.issueUrl);
17365
17167
  if (removed) {
17366
17168
  stateManager2.save();
17367
17169
  }
17368
- if (options.json) {
17369
- outputJson({ undismissed: removed, url: options.issueUrl });
17370
- } else if (removed) {
17371
- console.log(`Undismissed: ${options.issueUrl}`);
17372
- console.log("Issue reply notifications are active again.");
17373
- } else {
17374
- console.log("Issue was not dismissed.");
17375
- }
17170
+ return { undismissed: removed, url: options.issueUrl };
17376
17171
  }
17377
17172
  var init_dismiss = __esm({
17378
17173
  "src/commands/dismiss.ts"() {
17379
17174
  "use strict";
17380
17175
  init_core();
17381
- init_json();
17382
17176
  init_validation();
17383
17177
  }
17384
17178
  });
@@ -17391,77 +17185,41 @@ __export(snooze_exports, {
17391
17185
  });
17392
17186
  async function runSnooze(options) {
17393
17187
  validateUrl(options.prUrl);
17394
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
17188
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
17395
17189
  validateMessage(options.reason);
17396
17190
  const days = options.days ?? DEFAULT_SNOOZE_DAYS;
17397
17191
  if (!Number.isFinite(days) || days <= 0) {
17398
- if (options.json) {
17399
- outputJsonError("Snooze duration must be a positive number of days.");
17400
- } else {
17401
- console.error("Error: Snooze duration must be a positive number of days.");
17402
- }
17403
- process.exit(1);
17192
+ throw new Error("Snooze duration must be a positive number of days.");
17404
17193
  }
17405
- try {
17406
- const stateManager2 = getStateManager();
17407
- const added = stateManager2.snoozePR(options.prUrl, options.reason, days);
17408
- if (added) {
17409
- stateManager2.save();
17410
- }
17411
- const snoozeInfo = stateManager2.getSnoozeInfo(options.prUrl);
17412
- if (options.json) {
17413
- outputJson({
17414
- snoozed: added,
17415
- url: options.prUrl,
17416
- days,
17417
- reason: options.reason,
17418
- expiresAt: snoozeInfo?.expiresAt
17419
- });
17420
- } else if (added) {
17421
- console.log(`Snoozed: ${options.prUrl}`);
17422
- console.log(`Reason: ${options.reason}`);
17423
- console.log(`Duration: ${days} day${days === 1 ? "" : "s"}`);
17424
- console.log(`Expires: ${snoozeInfo?.expiresAt ? new Date(snoozeInfo.expiresAt).toLocaleString() : "unknown"}`);
17425
- console.log("CI failure notifications are now muted for this PR.");
17426
- } else {
17427
- console.log("PR is already snoozed.");
17428
- if (snoozeInfo) {
17429
- console.log(`Expires: ${new Date(snoozeInfo.expiresAt).toLocaleString()}`);
17430
- }
17431
- }
17432
- } catch (error) {
17433
- const msg = error instanceof Error ? error.message : String(error);
17434
- if (options.json) {
17435
- outputJsonError(`Snooze failed: ${msg}`);
17436
- } else {
17437
- console.error(`Error: Snooze failed: ${msg}`);
17438
- }
17439
- process.exit(1);
17194
+ const stateManager2 = getStateManager();
17195
+ const added = stateManager2.snoozePR(options.prUrl, options.reason, days);
17196
+ if (added) {
17197
+ stateManager2.save();
17440
17198
  }
17199
+ const snoozeInfo = stateManager2.getSnoozeInfo(options.prUrl);
17200
+ return {
17201
+ snoozed: added,
17202
+ url: options.prUrl,
17203
+ days,
17204
+ reason: options.reason,
17205
+ expiresAt: snoozeInfo?.expiresAt
17206
+ };
17441
17207
  }
17442
17208
  async function runUnsnooze(options) {
17443
17209
  validateUrl(options.prUrl);
17444
- validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR", options.json);
17210
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
17445
17211
  const stateManager2 = getStateManager();
17446
17212
  const removed = stateManager2.unsnoozePR(options.prUrl);
17447
17213
  if (removed) {
17448
17214
  stateManager2.save();
17449
17215
  }
17450
- if (options.json) {
17451
- outputJson({ unsnoozed: removed, url: options.prUrl });
17452
- } else if (removed) {
17453
- console.log(`Unsnoozed: ${options.prUrl}`);
17454
- console.log("CI failure notifications are active again for this PR.");
17455
- } else {
17456
- console.log("PR was not snoozed.");
17457
- }
17216
+ return { unsnoozed: removed, url: options.prUrl };
17458
17217
  }
17459
17218
  var DEFAULT_SNOOZE_DAYS;
17460
17219
  var init_snooze = __esm({
17461
17220
  "src/commands/snooze.ts"() {
17462
17221
  "use strict";
17463
17222
  init_core();
17464
- init_json();
17465
17223
  init_validation();
17466
17224
  DEFAULT_SNOOZE_DAYS = 7;
17467
17225
  }
@@ -17486,16 +17244,26 @@ var {
17486
17244
 
17487
17245
  // src/cli.ts
17488
17246
  init_core();
17489
- var VERSION10 = (() => {
17490
- try {
17491
- const fs9 = require("fs");
17492
- const path9 = require("path");
17493
- const pkgPath = path9.join(path9.dirname(process.argv[1]), "..", "package.json");
17494
- return JSON.parse(fs9.readFileSync(pkgPath, "utf-8")).version;
17495
- } catch (_err) {
17496
- return "0.0.0";
17247
+ init_errors();
17248
+ init_json();
17249
+ function printRepos(repos) {
17250
+ const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
17251
+ for (const [remote, info] of entries) {
17252
+ const branch = info.currentBranch ? ` (${info.currentBranch})` : "";
17253
+ console.log(` ${remote}${branch}`);
17254
+ console.log(` ${info.path}`);
17497
17255
  }
17498
- })();
17256
+ }
17257
+ function handleCommandError(err, json) {
17258
+ const msg = errorMessage(err);
17259
+ if (json) {
17260
+ outputJsonError(msg);
17261
+ } else {
17262
+ console.error(`Error: ${msg}`);
17263
+ }
17264
+ process.exit(1);
17265
+ }
17266
+ var VERSION10 = getCLIVersion();
17499
17267
  var LOCAL_ONLY_COMMANDS = [
17500
17268
  "help",
17501
17269
  "status",
@@ -17506,6 +17274,7 @@ var LOCAL_ONLY_COMMANDS = [
17506
17274
  "setup",
17507
17275
  "checkSetup",
17508
17276
  "dashboard",
17277
+ "serve",
17509
17278
  "parse-issue-list",
17510
17279
  "check-integration",
17511
17280
  "local-repos",
@@ -17520,106 +17289,571 @@ var LOCAL_ONLY_COMMANDS = [
17520
17289
  var program2 = new Command();
17521
17290
  program2.name("oss-autopilot").description("AI-powered autopilot for managing open source contributions").version(VERSION10).option("--debug", "Enable debug logging");
17522
17291
  program2.command("daily").description("Run daily check on all tracked PRs").option("--json", "Output as JSON").action(async (options) => {
17523
- const { runDaily: runDaily2 } = await Promise.resolve().then(() => (init_daily(), daily_exports));
17524
- await runDaily2({ json: options.json });
17292
+ try {
17293
+ if (options.json) {
17294
+ const { runDaily: runDaily2 } = await Promise.resolve().then(() => (init_daily(), daily_exports));
17295
+ const data = await runDaily2();
17296
+ outputJson(data);
17297
+ } else {
17298
+ const { runDailyForDisplay: runDailyForDisplay2, printDigest: printDigest2 } = await Promise.resolve().then(() => (init_daily(), daily_exports));
17299
+ const result = await runDailyForDisplay2();
17300
+ printDigest2(result.digest, result.capacity, result.commentedIssues);
17301
+ }
17302
+ } catch (err) {
17303
+ handleCommandError(err, options.json);
17304
+ }
17525
17305
  });
17526
17306
  program2.command("status").description("Show current status and stats").option("--json", "Output as JSON").option("--offline", "Use cached data only (no GitHub API calls)").action(async (options) => {
17527
- const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
17528
- await runStatus2({ json: options.json, offline: options.offline });
17307
+ try {
17308
+ const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
17309
+ const data = await runStatus2({ offline: options.offline });
17310
+ if (options.json) {
17311
+ outputJson(data);
17312
+ } else {
17313
+ console.log("\n\u{1F4CA} OSS Status\n");
17314
+ console.log(`Merged PRs: ${data.stats.mergedPRs}`);
17315
+ console.log(`Closed PRs: ${data.stats.closedPRs}`);
17316
+ console.log(`Merge Rate: ${data.stats.mergeRate}`);
17317
+ console.log(`Needs Response: ${data.stats.needsResponse}`);
17318
+ if (data.offline) {
17319
+ console.log(`
17320
+ Last Updated: ${data.lastUpdated || "Never"}`);
17321
+ console.log("(Offline mode: showing cached data)");
17322
+ } else {
17323
+ console.log(`
17324
+ Last Run: ${data.lastRunAt || "Never"}`);
17325
+ }
17326
+ console.log("\nRun with --json for structured output");
17327
+ }
17328
+ } catch (err) {
17329
+ handleCommandError(err, options.json);
17330
+ }
17529
17331
  });
17530
17332
  program2.command("search [count]").description("Search for new issues to work on").option("--json", "Output as JSON").action(async (count, options) => {
17531
- const { runSearch: runSearch2 } = await Promise.resolve().then(() => (init_search(), search_exports));
17532
- await runSearch2({ maxResults: parseInt(count) || 5, json: options.json });
17333
+ try {
17334
+ const { runSearch: runSearch2 } = await Promise.resolve().then(() => (init_search(), search_exports));
17335
+ if (!options.json) {
17336
+ console.log(`
17337
+ Searching for issues (max ${parseInt(count) || 5})...
17338
+ `);
17339
+ }
17340
+ const data = await runSearch2({ maxResults: parseInt(count) || 5 });
17341
+ if (options.json) {
17342
+ outputJson(data);
17343
+ } else {
17344
+ if (data.candidates.length === 0) {
17345
+ if (data.rateLimitWarning) {
17346
+ console.warn(`
17347
+ ${data.rateLimitWarning}
17348
+ `);
17349
+ } else {
17350
+ console.log("No matching issues found.");
17351
+ }
17352
+ return;
17353
+ }
17354
+ if (data.rateLimitWarning) {
17355
+ console.warn(`
17356
+ ${data.rateLimitWarning}
17357
+ `);
17358
+ }
17359
+ console.log(`Found ${data.candidates.length} candidates:
17360
+ `);
17361
+ for (const candidate of data.candidates) {
17362
+ const { issue, recommendation, reasonsToApprove, reasonsToSkip, viabilityScore } = candidate;
17363
+ console.log(`[${recommendation.toUpperCase()}] ${issue.repo}#${issue.number}: ${issue.title}`);
17364
+ console.log(` URL: ${issue.url}`);
17365
+ console.log(` Viability: ${viabilityScore}/100`);
17366
+ if (reasonsToApprove.length > 0) console.log(` Approve: ${reasonsToApprove.join(", ")}`);
17367
+ if (reasonsToSkip.length > 0) console.log(` Skip: ${reasonsToSkip.join(", ")}`);
17368
+ console.log("---");
17369
+ }
17370
+ }
17371
+ } catch (err) {
17372
+ handleCommandError(err, options.json);
17373
+ }
17533
17374
  });
17534
17375
  program2.command("vet <issue-url>").description("Vet a specific issue before working on it").option("--json", "Output as JSON").action(async (issueUrl, options) => {
17535
- const { runVet: runVet2 } = await Promise.resolve().then(() => (init_vet(), vet_exports));
17536
- await runVet2({ issueUrl, json: options.json });
17376
+ try {
17377
+ const { runVet: runVet2 } = await Promise.resolve().then(() => (init_vet(), vet_exports));
17378
+ const data = await runVet2({ issueUrl });
17379
+ if (options.json) {
17380
+ outputJson(data);
17381
+ } else {
17382
+ const { issue, recommendation, reasonsToApprove, reasonsToSkip } = data;
17383
+ console.log(`
17384
+ Vetting issue: ${issueUrl}
17385
+ `);
17386
+ console.log(`[${recommendation.toUpperCase()}] ${issue.repo}#${issue.number}: ${issue.title}`);
17387
+ console.log(` URL: ${issue.url}`);
17388
+ if (reasonsToApprove.length > 0) console.log(` Approve: ${reasonsToApprove.join(", ")}`);
17389
+ if (reasonsToSkip.length > 0) console.log(` Skip: ${reasonsToSkip.join(", ")}`);
17390
+ }
17391
+ } catch (err) {
17392
+ handleCommandError(err, options.json);
17393
+ }
17537
17394
  });
17538
17395
  program2.command("track <pr-url>").description("Add a PR to track").option("--json", "Output as JSON").action(async (prUrl, options) => {
17539
- const { runTrack: runTrack2 } = await Promise.resolve().then(() => (init_track(), track_exports));
17540
- await runTrack2({ prUrl, json: options.json });
17396
+ try {
17397
+ const { runTrack: runTrack2 } = await Promise.resolve().then(() => (init_track(), track_exports));
17398
+ const data = await runTrack2({ prUrl });
17399
+ if (options.json) {
17400
+ outputJson(data);
17401
+ } else {
17402
+ console.log(`
17403
+ PR: ${data.pr.repo}#${data.pr.number} - ${data.pr.title}`);
17404
+ console.log("Note: In v2, PRs are tracked automatically via the daily run.");
17405
+ }
17406
+ } catch (err) {
17407
+ handleCommandError(err, options.json);
17408
+ }
17541
17409
  });
17542
17410
  program2.command("untrack <pr-url>").description("Stop tracking a PR").option("--json", "Output as JSON").action(async (prUrl, options) => {
17543
- const { runUntrack: runUntrack2 } = await Promise.resolve().then(() => (init_track(), track_exports));
17544
- await runUntrack2({ prUrl, json: options.json });
17411
+ try {
17412
+ const { runUntrack: runUntrack2 } = await Promise.resolve().then(() => (init_track(), track_exports));
17413
+ const data = await runUntrack2({ prUrl });
17414
+ if (options.json) {
17415
+ outputJson(data);
17416
+ } else {
17417
+ console.log(
17418
+ "Note: In v2, PRs are fetched fresh on each daily run \u2014 there is no local tracking list to remove from."
17419
+ );
17420
+ console.log("Use `shelve` to temporarily hide a PR from the daily summary.");
17421
+ }
17422
+ } catch (err) {
17423
+ handleCommandError(err, options.json);
17424
+ }
17545
17425
  });
17546
17426
  program2.command("read [pr-url]").description("Mark PR comments as read").option("--all", "Mark all PRs as read").option("--json", "Output as JSON").action(async (prUrl, options) => {
17547
- const { runRead: runRead2 } = await Promise.resolve().then(() => (init_read(), read_exports));
17548
- await runRead2({ prUrl, all: options.all, json: options.json });
17427
+ try {
17428
+ const { runRead: runRead2 } = await Promise.resolve().then(() => (init_read(), read_exports));
17429
+ const data = await runRead2({ prUrl, all: options.all });
17430
+ if (options.json) {
17431
+ outputJson(data);
17432
+ } else {
17433
+ console.log("Note: In v2, PR read state is not tracked locally. PRs are fetched fresh on each daily run.");
17434
+ }
17435
+ } catch (err) {
17436
+ handleCommandError(err, options.json);
17437
+ }
17549
17438
  });
17550
17439
  program2.command("comments <pr-url>").description("Show all comments on a PR").option("--bots", "Include bot comments").option("--json", "Output as JSON").action(async (prUrl, options) => {
17551
- const { runComments: runComments2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17552
- await runComments2({ prUrl, showBots: options.bots, json: options.json });
17440
+ try {
17441
+ const { runComments: runComments2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17442
+ const data = await runComments2({ prUrl, showBots: options.bots });
17443
+ if (options.json) {
17444
+ outputJson(data);
17445
+ } else {
17446
+ console.log(`
17447
+ Fetching comments for: ${prUrl}
17448
+ `);
17449
+ console.log(`## ${data.pr.title}
17450
+ `);
17451
+ console.log(`**Status:** ${data.pr.state} | **Mergeable:** ${data.pr.mergeable ?? "checking..."}`);
17452
+ console.log(`**Branch:** ${data.pr.head} -> ${data.pr.base}`);
17453
+ console.log(`**URL:** ${data.pr.url}
17454
+ `);
17455
+ const REVIEW_STATE_LABELS = {
17456
+ APPROVED: "[Approved]",
17457
+ CHANGES_REQUESTED: "[Changes]"
17458
+ };
17459
+ if (data.reviews.length > 0) {
17460
+ console.log("### Reviews (newest first)\n");
17461
+ for (const review of data.reviews) {
17462
+ const state = REVIEW_STATE_LABELS[review.state] ?? "[Comment]";
17463
+ const time = review.submittedAt ? formatRelativeTime(review.submittedAt) : "";
17464
+ console.log(`${state} **@${review.user}** (${review.state}) - ${time}`);
17465
+ if (review.body) {
17466
+ console.log(`> ${review.body.split("\n").join("\n> ")}
17467
+ `);
17468
+ }
17469
+ }
17470
+ }
17471
+ if (data.reviewComments.length > 0) {
17472
+ console.log("### Inline Comments (newest first)\n");
17473
+ for (const comment of data.reviewComments) {
17474
+ const time = formatRelativeTime(comment.createdAt);
17475
+ console.log(`**@${comment.user}** on \`${comment.path}\` - ${time}`);
17476
+ console.log(`> ${comment.body.split("\n").join("\n> ")}`);
17477
+ console.log("");
17478
+ }
17479
+ }
17480
+ if (data.issueComments.length > 0) {
17481
+ console.log("### Discussion (newest first)\n");
17482
+ for (const comment of data.issueComments) {
17483
+ const time = formatRelativeTime(comment.createdAt);
17484
+ console.log(`**@${comment.user}** - ${time}`);
17485
+ console.log(`> ${comment.body?.split("\n").join("\n> ")}
17486
+ `);
17487
+ }
17488
+ }
17489
+ if (data.reviewComments.length === 0 && data.issueComments.length === 0 && data.reviews.length === 0) {
17490
+ console.log("No comments from other users.\n");
17491
+ }
17492
+ console.log("---");
17493
+ console.log(
17494
+ `**Summary:** ${data.summary.reviewCount} reviews, ${data.summary.inlineCommentCount} inline comments, ${data.summary.discussionCommentCount} discussion comments`
17495
+ );
17496
+ }
17497
+ } catch (err) {
17498
+ handleCommandError(err, options.json);
17499
+ }
17553
17500
  });
17554
17501
  program2.command("post <url> [message...]").description("Post a comment to a PR or issue").option("--stdin", "Read message from stdin").option("--json", "Output as JSON").action(async (url, messageParts, options) => {
17555
- const { runPost: runPost2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17556
- const message = options.stdin ? void 0 : messageParts.join(" ");
17557
- await runPost2({ url, message, stdin: options.stdin, json: options.json });
17502
+ try {
17503
+ let message;
17504
+ if (options.stdin) {
17505
+ const chunks = [];
17506
+ for await (const chunk of process.stdin) {
17507
+ chunks.push(chunk);
17508
+ }
17509
+ message = Buffer.concat(chunks).toString("utf-8").trim();
17510
+ } else {
17511
+ message = messageParts.join(" ");
17512
+ }
17513
+ const { runPost: runPost2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17514
+ const data = await runPost2({ url, message });
17515
+ if (options.json) {
17516
+ outputJson(data);
17517
+ } else {
17518
+ console.log(`Comment posted: ${data.commentUrl}`);
17519
+ }
17520
+ } catch (err) {
17521
+ handleCommandError(err, options.json);
17522
+ }
17558
17523
  });
17559
17524
  program2.command("claim <issue-url> [message...]").description("Claim an issue by posting a comment").option("--json", "Output as JSON").action(async (issueUrl, messageParts, options) => {
17560
- const { runClaim: runClaim2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17561
- const message = messageParts.length > 0 ? messageParts.join(" ") : void 0;
17562
- await runClaim2({ issueUrl, message, json: options.json });
17525
+ try {
17526
+ const { runClaim: runClaim2 } = await Promise.resolve().then(() => (init_comments(), comments_exports));
17527
+ const message = messageParts.length > 0 ? messageParts.join(" ") : void 0;
17528
+ const data = await runClaim2({ issueUrl, message });
17529
+ if (options.json) {
17530
+ outputJson(data);
17531
+ } else {
17532
+ console.log(`Issue claimed: ${data.commentUrl}`);
17533
+ }
17534
+ } catch (err) {
17535
+ handleCommandError(err, options.json);
17536
+ }
17563
17537
  });
17564
17538
  program2.command("config [key] [value]").description("Show or update configuration").option("--json", "Output as JSON").action(async (key, value, options) => {
17565
- const { runConfig: runConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
17566
- await runConfig2({ key, value, json: options.json });
17539
+ try {
17540
+ const { runConfig: runConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
17541
+ const data = await runConfig2({ key, value });
17542
+ if (options.json) {
17543
+ outputJson(data);
17544
+ } else if ("config" in data) {
17545
+ console.log("\n\u2699\uFE0F Current Configuration:\n");
17546
+ console.log(JSON.stringify(data.config, null, 2));
17547
+ } else {
17548
+ console.log(`Set ${data.key} to: ${data.value}`);
17549
+ }
17550
+ } catch (err) {
17551
+ handleCommandError(err, options.json);
17552
+ }
17567
17553
  });
17568
17554
  program2.command("init <username>").description("Initialize with your GitHub username and import open PRs").option("--json", "Output as JSON").action(async (username, options) => {
17569
- const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
17570
- await runInit2({ username, json: options.json });
17555
+ try {
17556
+ const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
17557
+ const data = await runInit2({ username });
17558
+ if (options.json) {
17559
+ outputJson(data);
17560
+ } else {
17561
+ console.log(`
17562
+ Username set to @${data.username}.`);
17563
+ console.log("Run `oss-autopilot daily` to fetch your open PRs from GitHub.");
17564
+ }
17565
+ } catch (err) {
17566
+ handleCommandError(err, options.json);
17567
+ }
17571
17568
  });
17572
17569
  program2.command("setup").description("Interactive setup / configuration").option("--reset", "Re-run setup even if already complete").option("--set <settings...>", "Set specific values (key=value)").option("--json", "Output as JSON").action(async (options) => {
17573
- const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
17574
- await runSetup2({ reset: options.reset, set: options.set, json: options.json });
17570
+ try {
17571
+ const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
17572
+ const data = await runSetup2({ reset: options.reset, set: options.set });
17573
+ if (options.json) {
17574
+ outputJson(data);
17575
+ } else if ("success" in data) {
17576
+ for (const [key, value] of Object.entries(data.settings)) {
17577
+ console.log(`\u2713 ${key}: ${value}`);
17578
+ }
17579
+ if (data.warnings) {
17580
+ for (const w of data.warnings) {
17581
+ console.warn(w);
17582
+ }
17583
+ }
17584
+ } else if ("setupComplete" in data && data.setupComplete) {
17585
+ console.log("\n\u2699\uFE0F OSS Autopilot Setup\n");
17586
+ console.log("\u2713 Setup already complete!\n");
17587
+ console.log("Current settings:");
17588
+ console.log(` GitHub username: ${data.config.githubUsername || "(not set)"}`);
17589
+ console.log(` Max active PRs: ${data.config.maxActivePRs}`);
17590
+ console.log(` Dormant threshold: ${data.config.dormantThresholdDays} days`);
17591
+ console.log(` Approaching dormant: ${data.config.approachingDormantDays} days`);
17592
+ console.log(` Languages: ${data.config.languages.join(", ")}`);
17593
+ console.log(` Labels: ${data.config.labels.join(", ")}`);
17594
+ console.log(`
17595
+ Run 'setup --reset' to reconfigure.`);
17596
+ } else if ("setupRequired" in data) {
17597
+ console.log("\n\u2699\uFE0F OSS Autopilot Setup\n");
17598
+ console.log("SETUP_REQUIRED");
17599
+ console.log("---");
17600
+ console.log("Please configure the following settings:\n");
17601
+ for (const prompt of data.prompts) {
17602
+ console.log(`SETTING: ${prompt.setting}`);
17603
+ console.log(`PROMPT: ${prompt.prompt}`);
17604
+ const currentVal = Array.isArray(prompt.current) ? prompt.current.join(", ") : prompt.current;
17605
+ console.log(`CURRENT: ${currentVal ?? "(not set)"}`);
17606
+ if (prompt.required) console.log("REQUIRED: true");
17607
+ if (prompt.default !== void 0) {
17608
+ const defaultVal = Array.isArray(prompt.default) ? prompt.default.join(", ") : prompt.default;
17609
+ console.log(`DEFAULT: ${defaultVal}`);
17610
+ }
17611
+ if (prompt.type) console.log(`TYPE: ${prompt.type}`);
17612
+ console.log("");
17613
+ }
17614
+ console.log("---");
17615
+ console.log("END_SETUP_PROMPTS");
17616
+ }
17617
+ } catch (err) {
17618
+ handleCommandError(err, options.json);
17619
+ }
17575
17620
  });
17576
17621
  program2.command("checkSetup").description("Check if setup is complete").option("--json", "Output as JSON").action(async (options) => {
17577
- const { runCheckSetup: runCheckSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
17578
- await runCheckSetup2({ json: options.json });
17622
+ try {
17623
+ const { runCheckSetup: runCheckSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
17624
+ const data = await runCheckSetup2();
17625
+ if (options.json) {
17626
+ outputJson(data);
17627
+ } else if (data.setupComplete) {
17628
+ console.log("SETUP_COMPLETE");
17629
+ console.log(`username=${data.username}`);
17630
+ } else {
17631
+ console.log("SETUP_INCOMPLETE");
17632
+ }
17633
+ } catch (err) {
17634
+ handleCommandError(err, options.json);
17635
+ }
17579
17636
  });
17580
- program2.command("dashboard").description("Generate HTML stats dashboard").option("--open", "Open in browser").option("--json", "Output as JSON").option("--offline", "Use cached data only (no GitHub API calls)").action(async (options) => {
17637
+ var dashboardCmd = program2.command("dashboard").description("Dashboard commands");
17638
+ dashboardCmd.command("serve").description("Start interactive dashboard server").option("--port <port>", "Port to listen on", "3000").option("--no-open", "Do not open browser automatically").action(async (options) => {
17639
+ const port = parseInt(options.port, 10);
17640
+ if (isNaN(port) || port < 1 || port > 65535) {
17641
+ console.error(`Invalid port number: "${options.port}". Must be an integer between 1 and 65535.`);
17642
+ process.exit(1);
17643
+ }
17644
+ const { serveDashboard: serveDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
17645
+ await serveDashboard2({ port, open: options.open });
17646
+ });
17647
+ dashboardCmd.option("--open", "Open in browser").option("--json", "Output as JSON").option("--offline", "Use cached data only (no GitHub API calls)").action(async (options) => {
17581
17648
  const { runDashboard: runDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
17582
17649
  await runDashboard2({ open: options.open, json: options.json, offline: options.offline });
17583
17650
  });
17584
17651
  program2.command("parse-issue-list <path>").description("Parse a markdown issue list into structured JSON").option("--json", "Output as JSON").action(async (filePath, options) => {
17585
- const { runParseList: runParseList2 } = await Promise.resolve().then(() => (init_parse_list(), parse_list_exports));
17586
- await runParseList2({ filePath, json: options.json });
17652
+ try {
17653
+ const { runParseList: runParseList2 } = await Promise.resolve().then(() => (init_parse_list(), parse_list_exports));
17654
+ const data = await runParseList2({ filePath });
17655
+ if (options.json) {
17656
+ outputJson(data);
17657
+ } else {
17658
+ const path10 = await import("path");
17659
+ const resolvedPath = path10.resolve(filePath);
17660
+ console.log(`
17661
+ \u{1F4CB} Issue List: ${resolvedPath}
17662
+ `);
17663
+ console.log(`Available: ${data.availableCount} | Completed: ${data.completedCount}
17664
+ `);
17665
+ if (data.available.length > 0) {
17666
+ console.log("--- Available ---");
17667
+ for (const item of data.available) {
17668
+ console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
17669
+ }
17670
+ }
17671
+ if (data.completed.length > 0) {
17672
+ console.log("\n--- Completed ---");
17673
+ for (const item of data.completed) {
17674
+ console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
17675
+ }
17676
+ }
17677
+ }
17678
+ } catch (err) {
17679
+ handleCommandError(err, options.json);
17680
+ }
17587
17681
  });
17588
17682
  program2.command("check-integration").description("Detect new files not referenced by the codebase").option("--base <branch>", "Base branch to compare against", "main").option("--json", "Output as JSON").action(async (options) => {
17589
- const { runCheckIntegration: runCheckIntegration2 } = await Promise.resolve().then(() => (init_check_integration(), check_integration_exports));
17590
- await runCheckIntegration2({ base: options.base, json: options.json });
17683
+ try {
17684
+ const { runCheckIntegration: runCheckIntegration2 } = await Promise.resolve().then(() => (init_check_integration(), check_integration_exports));
17685
+ const data = await runCheckIntegration2({ base: options.base });
17686
+ if (options.json) {
17687
+ outputJson(data);
17688
+ } else if (data.newFiles.length === 0) {
17689
+ console.log("\nNo new code files to check.");
17690
+ } else {
17691
+ console.log(`
17692
+ \u{1F50D} Integration Check (base: ${options.base})
17693
+ `);
17694
+ console.log(`New files: ${data.newFiles.length} | Unreferenced: ${data.unreferencedCount}
17695
+ `);
17696
+ for (const file of data.newFiles) {
17697
+ const status = file.isIntegrated ? "\u2705" : "\u26A0\uFE0F";
17698
+ console.log(`${status} ${file.path}`);
17699
+ if (file.isIntegrated) {
17700
+ console.log(` Referenced by: ${file.referencedBy.join(", ")}`);
17701
+ } else {
17702
+ console.log(" Not referenced by any file");
17703
+ if (file.suggestedEntryPoints && file.suggestedEntryPoints.length > 0) {
17704
+ console.log(` Suggested entry points: ${file.suggestedEntryPoints.join(", ")}`);
17705
+ }
17706
+ }
17707
+ }
17708
+ }
17709
+ } catch (err) {
17710
+ handleCommandError(err, options.json);
17711
+ }
17591
17712
  });
17592
17713
  program2.command("local-repos").description("Scan filesystem for local git clones").option("--scan", "Force re-scan (ignores cache)").option("--paths <dirs...>", "Directories to scan").option("--json", "Output as JSON").action(async (options) => {
17593
- const { runLocalRepos: runLocalRepos2 } = await Promise.resolve().then(() => (init_local_repos(), local_repos_exports));
17594
- await runLocalRepos2({ scan: options.scan, paths: options.paths, json: options.json });
17714
+ try {
17715
+ const { runLocalRepos: runLocalRepos2 } = await Promise.resolve().then(() => (init_local_repos(), local_repos_exports));
17716
+ const data = await runLocalRepos2({ scan: options.scan, paths: options.paths });
17717
+ if (options.json) {
17718
+ outputJson(data);
17719
+ } else if (data.fromCache) {
17720
+ console.log(`
17721
+ \u{1F4C1} Local Repos (cached ${data.cachedAt})
17722
+ `);
17723
+ printRepos(data.repos);
17724
+ } else {
17725
+ console.log(`Found ${Object.keys(data.repos).length} repos:
17726
+ `);
17727
+ printRepos(data.repos);
17728
+ }
17729
+ } catch (err) {
17730
+ handleCommandError(err, options.json);
17731
+ }
17595
17732
  });
17596
17733
  program2.command("startup").description("Run all pre-flight checks and daily fetch in one call").option("--json", "Output as JSON").action(async (options) => {
17597
- const { runStartup: runStartup2 } = await Promise.resolve().then(() => (init_startup(), startup_exports));
17598
- await runStartup2({ json: options.json });
17734
+ try {
17735
+ const { runStartup: runStartup2 } = await Promise.resolve().then(() => (init_startup(), startup_exports));
17736
+ const data = await runStartup2();
17737
+ if (options.json) {
17738
+ outputJson(data);
17739
+ } else {
17740
+ if (!data.setupComplete) {
17741
+ console.log("Setup incomplete. Run /setup-oss first.");
17742
+ } else if (data.authError) {
17743
+ console.error(`Error: ${data.authError}`);
17744
+ } else {
17745
+ console.log(`OSS Autopilot v${data.version}`);
17746
+ console.log(data.daily?.briefSummary ?? "");
17747
+ if (data.dashboardPath) console.log(`Dashboard: ${data.dashboardPath}`);
17748
+ }
17749
+ }
17750
+ } catch (err) {
17751
+ handleCommandError(err, options.json);
17752
+ }
17599
17753
  });
17600
17754
  program2.command("shelve <pr-url>").description("Shelve a PR (exclude from capacity and actionable issues)").option("--json", "Output as JSON").action(async (prUrl, options) => {
17601
- const { runShelve: runShelve2 } = await Promise.resolve().then(() => (init_shelve(), shelve_exports));
17602
- await runShelve2({ prUrl, json: options.json });
17755
+ try {
17756
+ const { runShelve: runShelve2 } = await Promise.resolve().then(() => (init_shelve(), shelve_exports));
17757
+ const data = await runShelve2({ prUrl });
17758
+ if (options.json) {
17759
+ outputJson(data);
17760
+ } else if (data.shelved) {
17761
+ console.log(`Shelved: ${prUrl}`);
17762
+ console.log("This PR is now excluded from capacity and actionable issues.");
17763
+ console.log("It will auto-unshelve if a maintainer engages.");
17764
+ } else {
17765
+ console.log("PR is already shelved.");
17766
+ }
17767
+ } catch (err) {
17768
+ handleCommandError(err, options.json);
17769
+ }
17603
17770
  });
17604
17771
  program2.command("unshelve <pr-url>").description("Unshelve a PR (include in capacity and actionable issues again)").option("--json", "Output as JSON").action(async (prUrl, options) => {
17605
- const { runUnshelve: runUnshelve2 } = await Promise.resolve().then(() => (init_shelve(), shelve_exports));
17606
- await runUnshelve2({ prUrl, json: options.json });
17772
+ try {
17773
+ const { runUnshelve: runUnshelve2 } = await Promise.resolve().then(() => (init_shelve(), shelve_exports));
17774
+ const data = await runUnshelve2({ prUrl });
17775
+ if (options.json) {
17776
+ outputJson(data);
17777
+ } else if (data.unshelved) {
17778
+ console.log(`Unshelved: ${prUrl}`);
17779
+ console.log("This PR is now active again.");
17780
+ } else {
17781
+ console.log("PR was not shelved.");
17782
+ }
17783
+ } catch (err) {
17784
+ handleCommandError(err, options.json);
17785
+ }
17607
17786
  });
17608
17787
  program2.command("dismiss <issue-url>").description("Dismiss issue reply notifications (resurfaces on new activity)").option("--json", "Output as JSON").action(async (issueUrl, options) => {
17609
- const { runDismiss: runDismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
17610
- await runDismiss2({ issueUrl, json: options.json });
17788
+ try {
17789
+ const { runDismiss: runDismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
17790
+ const data = await runDismiss2({ issueUrl });
17791
+ if (options.json) {
17792
+ outputJson(data);
17793
+ } else if (data.dismissed) {
17794
+ console.log(`Dismissed: ${issueUrl}`);
17795
+ console.log("Issue reply notifications are now muted.");
17796
+ console.log("New responses after this point will resurface automatically.");
17797
+ } else {
17798
+ console.log("Issue is already dismissed.");
17799
+ }
17800
+ } catch (err) {
17801
+ handleCommandError(err, options.json);
17802
+ }
17611
17803
  });
17612
17804
  program2.command("undismiss <issue-url>").description("Undismiss an issue (re-enable reply notifications)").option("--json", "Output as JSON").action(async (issueUrl, options) => {
17613
- const { runUndismiss: runUndismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
17614
- await runUndismiss2({ issueUrl, json: options.json });
17805
+ try {
17806
+ const { runUndismiss: runUndismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
17807
+ const data = await runUndismiss2({ issueUrl });
17808
+ if (options.json) {
17809
+ outputJson(data);
17810
+ } else if (data.undismissed) {
17811
+ console.log(`Undismissed: ${issueUrl}`);
17812
+ console.log("Issue reply notifications are active again.");
17813
+ } else {
17814
+ console.log("Issue was not dismissed.");
17815
+ }
17816
+ } catch (err) {
17817
+ handleCommandError(err, options.json);
17818
+ }
17615
17819
  });
17616
17820
  program2.command("snooze <pr-url>").description("Snooze CI failure notifications for a PR").requiredOption("--reason <reason>", 'Reason for snoozing (e.g., "upstream infrastructure issue")').option("--days <days>", "Number of days to snooze (default: 7)", "7").option("--json", "Output as JSON").action(async (prUrl, options) => {
17617
- const { runSnooze: runSnooze2 } = await Promise.resolve().then(() => (init_snooze(), snooze_exports));
17618
- await runSnooze2({ prUrl, reason: options.reason, days: parseInt(options.days, 10), json: options.json });
17821
+ try {
17822
+ const { runSnooze: runSnooze2 } = await Promise.resolve().then(() => (init_snooze(), snooze_exports));
17823
+ const data = await runSnooze2({ prUrl, reason: options.reason, days: parseInt(options.days, 10) });
17824
+ if (options.json) {
17825
+ outputJson(data);
17826
+ } else if (data.snoozed) {
17827
+ console.log(`Snoozed: ${prUrl}`);
17828
+ console.log(`Reason: ${data.reason}`);
17829
+ console.log(`Duration: ${data.days} day${data.days === 1 ? "" : "s"}`);
17830
+ console.log(`Expires: ${data.expiresAt ? new Date(data.expiresAt).toLocaleString() : "unknown"}`);
17831
+ console.log("CI failure notifications are now muted for this PR.");
17832
+ } else {
17833
+ console.log("PR is already snoozed.");
17834
+ if (data.expiresAt) {
17835
+ console.log(`Expires: ${new Date(data.expiresAt).toLocaleString()}`);
17836
+ }
17837
+ }
17838
+ } catch (err) {
17839
+ handleCommandError(err, options.json);
17840
+ }
17619
17841
  });
17620
17842
  program2.command("unsnooze <pr-url>").description("Unsnooze a PR (re-enable CI failure notifications)").option("--json", "Output as JSON").action(async (prUrl, options) => {
17621
- const { runUnsnooze: runUnsnooze2 } = await Promise.resolve().then(() => (init_snooze(), snooze_exports));
17622
- await runUnsnooze2({ prUrl, json: options.json });
17843
+ try {
17844
+ const { runUnsnooze: runUnsnooze2 } = await Promise.resolve().then(() => (init_snooze(), snooze_exports));
17845
+ const data = await runUnsnooze2({ prUrl });
17846
+ if (options.json) {
17847
+ outputJson(data);
17848
+ } else if (data.unsnoozed) {
17849
+ console.log(`Unsnoozed: ${prUrl}`);
17850
+ console.log("CI failure notifications are active again for this PR.");
17851
+ } else {
17852
+ console.log("PR was not snoozed.");
17853
+ }
17854
+ } catch (err) {
17855
+ handleCommandError(err, options.json);
17856
+ }
17623
17857
  });
17624
17858
  program2.hook("preAction", async (thisCommand, actionCommand) => {
17625
17859
  const globalOpts = thisCommand.opts();