@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
package/dist/cli.js CHANGED
@@ -10,21 +10,30 @@
10
10
  * async token fetch to avoid blocking the event loop on `gh auth token`.
11
11
  */
12
12
  import { Command } from 'commander';
13
- import { getGitHubTokenAsync, enableDebug, debug } from './core/index.js';
14
- const VERSION = (() => {
15
- try {
16
- // eslint-disable-next-line @typescript-eslint/no-require-imports
17
- const fs = require('fs');
18
- // eslint-disable-next-line @typescript-eslint/no-require-imports
19
- const path = require('path');
20
- const pkgPath = path.join(path.dirname(process.argv[1]), '..', 'package.json');
21
- return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
22
- }
23
- catch (_err) {
24
- // package.json may not be readable in all bundle/install configurations — fall back to safe default
25
- return '0.0.0';
26
- }
27
- })();
13
+ import { getGitHubTokenAsync, enableDebug, debug, formatRelativeTime, getCLIVersion } from './core/index.js';
14
+ import { errorMessage } from './core/errors.js';
15
+ import { outputJson, outputJsonError } from './formatters/json.js';
16
+ /** Print local repos in human-readable format */
17
+ function printRepos(repos) {
18
+ const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
19
+ for (const [remote, info] of entries) {
20
+ const branch = info.currentBranch ? ` (${info.currentBranch})` : '';
21
+ console.log(` ${remote}${branch}`);
22
+ console.log(` ${info.path}`);
23
+ }
24
+ }
25
+ /** Shared error handler for CLI action catch blocks. */
26
+ function handleCommandError(err, json) {
27
+ const msg = errorMessage(err);
28
+ if (json) {
29
+ outputJsonError(msg);
30
+ }
31
+ else {
32
+ console.error(`Error: ${msg}`);
33
+ }
34
+ process.exit(1);
35
+ }
36
+ const VERSION = getCLIVersion();
28
37
  // Commands that skip the preAction GitHub token check.
29
38
  // startup handles auth internally (returns authError in JSON instead of process.exit).
30
39
  const LOCAL_ONLY_COMMANDS = [
@@ -37,6 +46,7 @@ const LOCAL_ONLY_COMMANDS = [
37
46
  'setup',
38
47
  'checkSetup',
39
48
  'dashboard',
49
+ 'serve',
40
50
  'parse-issue-list',
41
51
  'check-integration',
42
52
  'local-repos',
@@ -60,8 +70,21 @@ program
60
70
  .description('Run daily check on all tracked PRs')
61
71
  .option('--json', 'Output as JSON')
62
72
  .action(async (options) => {
63
- const { runDaily } = await import('./commands/daily.js');
64
- await runDaily({ json: options.json });
73
+ try {
74
+ if (options.json) {
75
+ const { runDaily } = await import('./commands/daily.js');
76
+ const data = await runDaily();
77
+ outputJson(data);
78
+ }
79
+ else {
80
+ const { runDailyForDisplay, printDigest } = await import('./commands/daily.js');
81
+ const result = await runDailyForDisplay();
82
+ printDigest(result.digest, result.capacity, result.commentedIssues);
83
+ }
84
+ }
85
+ catch (err) {
86
+ handleCommandError(err, options.json);
87
+ }
65
88
  });
66
89
  // Status command
67
90
  program
@@ -70,8 +93,31 @@ program
70
93
  .option('--json', 'Output as JSON')
71
94
  .option('--offline', 'Use cached data only (no GitHub API calls)')
72
95
  .action(async (options) => {
73
- const { runStatus } = await import('./commands/status.js');
74
- await runStatus({ json: options.json, offline: options.offline });
96
+ try {
97
+ const { runStatus } = await import('./commands/status.js');
98
+ const data = await runStatus({ offline: options.offline });
99
+ if (options.json) {
100
+ outputJson(data);
101
+ }
102
+ else {
103
+ console.log('\n\ud83d\udcca OSS Status\n');
104
+ console.log(`Merged PRs: ${data.stats.mergedPRs}`);
105
+ console.log(`Closed PRs: ${data.stats.closedPRs}`);
106
+ console.log(`Merge Rate: ${data.stats.mergeRate}`);
107
+ console.log(`Needs Response: ${data.stats.needsResponse}`);
108
+ if (data.offline) {
109
+ console.log(`\nLast Updated: ${data.lastUpdated || 'Never'}`);
110
+ console.log('(Offline mode: showing cached data)');
111
+ }
112
+ else {
113
+ console.log(`\nLast Run: ${data.lastRunAt || 'Never'}`);
114
+ }
115
+ console.log('\nRun with --json for structured output');
116
+ }
117
+ }
118
+ catch (err) {
119
+ handleCommandError(err, options.json);
120
+ }
75
121
  });
76
122
  // Search command
77
123
  program
@@ -79,8 +125,46 @@ program
79
125
  .description('Search for new issues to work on')
80
126
  .option('--json', 'Output as JSON')
81
127
  .action(async (count, options) => {
82
- const { runSearch } = await import('./commands/search.js');
83
- await runSearch({ maxResults: parseInt(count) || 5, json: options.json });
128
+ try {
129
+ const { runSearch } = await import('./commands/search.js');
130
+ if (!options.json) {
131
+ console.log(`\nSearching for issues (max ${parseInt(count) || 5})...\n`);
132
+ }
133
+ const data = await runSearch({ maxResults: parseInt(count) || 5 });
134
+ if (options.json) {
135
+ outputJson(data);
136
+ }
137
+ else {
138
+ if (data.candidates.length === 0) {
139
+ if (data.rateLimitWarning) {
140
+ console.warn(`\n${data.rateLimitWarning}\n`);
141
+ }
142
+ else {
143
+ console.log('No matching issues found.');
144
+ }
145
+ return;
146
+ }
147
+ if (data.rateLimitWarning) {
148
+ console.warn(`\n${data.rateLimitWarning}\n`);
149
+ }
150
+ console.log(`Found ${data.candidates.length} candidates:\n`);
151
+ for (const candidate of data.candidates) {
152
+ // Simple text format for candidates
153
+ const { issue, recommendation, reasonsToApprove, reasonsToSkip, viabilityScore } = candidate;
154
+ console.log(`[${recommendation.toUpperCase()}] ${issue.repo}#${issue.number}: ${issue.title}`);
155
+ console.log(` URL: ${issue.url}`);
156
+ console.log(` Viability: ${viabilityScore}/100`);
157
+ if (reasonsToApprove.length > 0)
158
+ console.log(` Approve: ${reasonsToApprove.join(', ')}`);
159
+ if (reasonsToSkip.length > 0)
160
+ console.log(` Skip: ${reasonsToSkip.join(', ')}`);
161
+ console.log('---');
162
+ }
163
+ }
164
+ }
165
+ catch (err) {
166
+ handleCommandError(err, options.json);
167
+ }
84
168
  });
85
169
  // Vet command
86
170
  program
@@ -88,8 +172,26 @@ program
88
172
  .description('Vet a specific issue before working on it')
89
173
  .option('--json', 'Output as JSON')
90
174
  .action(async (issueUrl, options) => {
91
- const { runVet } = await import('./commands/vet.js');
92
- await runVet({ issueUrl, json: options.json });
175
+ try {
176
+ const { runVet } = await import('./commands/vet.js');
177
+ const data = await runVet({ issueUrl });
178
+ if (options.json) {
179
+ outputJson(data);
180
+ }
181
+ else {
182
+ const { issue, recommendation, reasonsToApprove, reasonsToSkip } = data;
183
+ console.log(`\nVetting issue: ${issueUrl}\n`);
184
+ console.log(`[${recommendation.toUpperCase()}] ${issue.repo}#${issue.number}: ${issue.title}`);
185
+ console.log(` URL: ${issue.url}`);
186
+ if (reasonsToApprove.length > 0)
187
+ console.log(` Approve: ${reasonsToApprove.join(', ')}`);
188
+ if (reasonsToSkip.length > 0)
189
+ console.log(` Skip: ${reasonsToSkip.join(', ')}`);
190
+ }
191
+ }
192
+ catch (err) {
193
+ handleCommandError(err, options.json);
194
+ }
93
195
  });
94
196
  // Track command
95
197
  program
@@ -97,8 +199,20 @@ program
97
199
  .description('Add a PR to track')
98
200
  .option('--json', 'Output as JSON')
99
201
  .action(async (prUrl, options) => {
100
- const { runTrack } = await import('./commands/track.js');
101
- await runTrack({ prUrl, json: options.json });
202
+ try {
203
+ const { runTrack } = await import('./commands/track.js');
204
+ const data = await runTrack({ prUrl });
205
+ if (options.json) {
206
+ outputJson(data);
207
+ }
208
+ else {
209
+ console.log(`\nPR: ${data.pr.repo}#${data.pr.number} - ${data.pr.title}`);
210
+ console.log('Note: In v2, PRs are tracked automatically via the daily run.');
211
+ }
212
+ }
213
+ catch (err) {
214
+ handleCommandError(err, options.json);
215
+ }
102
216
  });
103
217
  // Untrack command
104
218
  program
@@ -106,8 +220,20 @@ program
106
220
  .description('Stop tracking a PR')
107
221
  .option('--json', 'Output as JSON')
108
222
  .action(async (prUrl, options) => {
109
- const { runUntrack } = await import('./commands/track.js');
110
- await runUntrack({ prUrl, json: options.json });
223
+ try {
224
+ const { runUntrack } = await import('./commands/track.js');
225
+ const data = await runUntrack({ prUrl });
226
+ if (options.json) {
227
+ outputJson(data);
228
+ }
229
+ else {
230
+ console.log('Note: In v2, PRs are fetched fresh on each daily run \u2014 there is no local tracking list to remove from.');
231
+ console.log('Use `shelve` to temporarily hide a PR from the daily summary.');
232
+ }
233
+ }
234
+ catch (err) {
235
+ handleCommandError(err, options.json);
236
+ }
111
237
  });
112
238
  // Read command (mark as read)
113
239
  program
@@ -116,8 +242,19 @@ program
116
242
  .option('--all', 'Mark all PRs as read')
117
243
  .option('--json', 'Output as JSON')
118
244
  .action(async (prUrl, options) => {
119
- const { runRead } = await import('./commands/read.js');
120
- await runRead({ prUrl, all: options.all, json: options.json });
245
+ try {
246
+ const { runRead } = await import('./commands/read.js');
247
+ const data = await runRead({ prUrl, all: options.all });
248
+ if (options.json) {
249
+ outputJson(data);
250
+ }
251
+ else {
252
+ console.log('Note: In v2, PR read state is not tracked locally. PRs are fetched fresh on each daily run.');
253
+ }
254
+ }
255
+ catch (err) {
256
+ handleCommandError(err, options.json);
257
+ }
121
258
  });
122
259
  // Comments command
123
260
  program
@@ -126,8 +263,61 @@ program
126
263
  .option('--bots', 'Include bot comments')
127
264
  .option('--json', 'Output as JSON')
128
265
  .action(async (prUrl, options) => {
129
- const { runComments } = await import('./commands/comments.js');
130
- await runComments({ prUrl, showBots: options.bots, json: options.json });
266
+ try {
267
+ const { runComments } = await import('./commands/comments.js');
268
+ const data = await runComments({ prUrl, showBots: options.bots });
269
+ if (options.json) {
270
+ outputJson(data);
271
+ }
272
+ else {
273
+ // Text output
274
+ console.log(`\nFetching comments for: ${prUrl}\n`);
275
+ console.log(`## ${data.pr.title}\n`);
276
+ console.log(`**Status:** ${data.pr.state} | **Mergeable:** ${data.pr.mergeable ?? 'checking...'}`);
277
+ console.log(`**Branch:** ${data.pr.head} -> ${data.pr.base}`);
278
+ console.log(`**URL:** ${data.pr.url}\n`);
279
+ const REVIEW_STATE_LABELS = {
280
+ APPROVED: '[Approved]',
281
+ CHANGES_REQUESTED: '[Changes]',
282
+ };
283
+ if (data.reviews.length > 0) {
284
+ console.log('### Reviews (newest first)\n');
285
+ for (const review of data.reviews) {
286
+ const state = REVIEW_STATE_LABELS[review.state] ?? '[Comment]';
287
+ const time = review.submittedAt ? formatRelativeTime(review.submittedAt) : '';
288
+ console.log(`${state} **@${review.user}** (${review.state}) - ${time}`);
289
+ if (review.body) {
290
+ console.log(`> ${review.body.split('\n').join('\n> ')}\n`);
291
+ }
292
+ }
293
+ }
294
+ if (data.reviewComments.length > 0) {
295
+ console.log('### Inline Comments (newest first)\n');
296
+ for (const comment of data.reviewComments) {
297
+ const time = formatRelativeTime(comment.createdAt);
298
+ console.log(`**@${comment.user}** on \`${comment.path}\` - ${time}`);
299
+ console.log(`> ${comment.body.split('\n').join('\n> ')}`);
300
+ console.log('');
301
+ }
302
+ }
303
+ if (data.issueComments.length > 0) {
304
+ console.log('### Discussion (newest first)\n');
305
+ for (const comment of data.issueComments) {
306
+ const time = formatRelativeTime(comment.createdAt);
307
+ console.log(`**@${comment.user}** - ${time}`);
308
+ console.log(`> ${comment.body?.split('\n').join('\n> ')}\n`);
309
+ }
310
+ }
311
+ if (data.reviewComments.length === 0 && data.issueComments.length === 0 && data.reviews.length === 0) {
312
+ console.log('No comments from other users.\n');
313
+ }
314
+ console.log('---');
315
+ console.log(`**Summary:** ${data.summary.reviewCount} reviews, ${data.summary.inlineCommentCount} inline comments, ${data.summary.discussionCommentCount} discussion comments`);
316
+ }
317
+ }
318
+ catch (err) {
319
+ handleCommandError(err, options.json);
320
+ }
131
321
  });
132
322
  // Post command
133
323
  program
@@ -136,9 +326,30 @@ program
136
326
  .option('--stdin', 'Read message from stdin')
137
327
  .option('--json', 'Output as JSON')
138
328
  .action(async (url, messageParts, options) => {
139
- const { runPost } = await import('./commands/comments.js');
140
- const message = options.stdin ? undefined : messageParts.join(' ');
141
- await runPost({ url, message, stdin: options.stdin, json: options.json });
329
+ try {
330
+ let message;
331
+ if (options.stdin) {
332
+ const chunks = [];
333
+ for await (const chunk of process.stdin) {
334
+ chunks.push(chunk);
335
+ }
336
+ message = Buffer.concat(chunks).toString('utf-8').trim();
337
+ }
338
+ else {
339
+ message = messageParts.join(' ');
340
+ }
341
+ const { runPost } = await import('./commands/comments.js');
342
+ const data = await runPost({ url, message });
343
+ if (options.json) {
344
+ outputJson(data);
345
+ }
346
+ else {
347
+ console.log(`Comment posted: ${data.commentUrl}`);
348
+ }
349
+ }
350
+ catch (err) {
351
+ handleCommandError(err, options.json);
352
+ }
142
353
  });
143
354
  // Claim command
144
355
  program
@@ -146,9 +357,20 @@ program
146
357
  .description('Claim an issue by posting a comment')
147
358
  .option('--json', 'Output as JSON')
148
359
  .action(async (issueUrl, messageParts, options) => {
149
- const { runClaim } = await import('./commands/comments.js');
150
- const message = messageParts.length > 0 ? messageParts.join(' ') : undefined;
151
- await runClaim({ issueUrl, message, json: options.json });
360
+ try {
361
+ const { runClaim } = await import('./commands/comments.js');
362
+ const message = messageParts.length > 0 ? messageParts.join(' ') : undefined;
363
+ const data = await runClaim({ issueUrl, message });
364
+ if (options.json) {
365
+ outputJson(data);
366
+ }
367
+ else {
368
+ console.log(`Issue claimed: ${data.commentUrl}`);
369
+ }
370
+ }
371
+ catch (err) {
372
+ handleCommandError(err, options.json);
373
+ }
152
374
  });
153
375
  // Config command
154
376
  program
@@ -156,8 +378,23 @@ program
156
378
  .description('Show or update configuration')
157
379
  .option('--json', 'Output as JSON')
158
380
  .action(async (key, value, options) => {
159
- const { runConfig } = await import('./commands/config.js');
160
- await runConfig({ key, value, json: options.json });
381
+ try {
382
+ const { runConfig } = await import('./commands/config.js');
383
+ const data = await runConfig({ key, value });
384
+ if (options.json) {
385
+ outputJson(data);
386
+ }
387
+ else if ('config' in data) {
388
+ console.log('\n\u2699\ufe0f Current Configuration:\n');
389
+ console.log(JSON.stringify(data.config, null, 2));
390
+ }
391
+ else {
392
+ console.log(`Set ${data.key} to: ${data.value}`);
393
+ }
394
+ }
395
+ catch (err) {
396
+ handleCommandError(err, options.json);
397
+ }
161
398
  });
162
399
  // Init command
163
400
  program
@@ -165,8 +402,20 @@ program
165
402
  .description('Initialize with your GitHub username and import open PRs')
166
403
  .option('--json', 'Output as JSON')
167
404
  .action(async (username, options) => {
168
- const { runInit } = await import('./commands/init.js');
169
- await runInit({ username, json: options.json });
405
+ try {
406
+ const { runInit } = await import('./commands/init.js');
407
+ const data = await runInit({ username });
408
+ if (options.json) {
409
+ outputJson(data);
410
+ }
411
+ else {
412
+ console.log(`\nUsername set to @${data.username}.`);
413
+ console.log('Run `oss-autopilot daily` to fetch your open PRs from GitHub.');
414
+ }
415
+ }
416
+ catch (err) {
417
+ handleCommandError(err, options.json);
418
+ }
170
419
  });
171
420
  // Setup command
172
421
  program
@@ -176,8 +425,64 @@ program
176
425
  .option('--set <settings...>', 'Set specific values (key=value)')
177
426
  .option('--json', 'Output as JSON')
178
427
  .action(async (options) => {
179
- const { runSetup } = await import('./commands/setup.js');
180
- await runSetup({ reset: options.reset, set: options.set, json: options.json });
428
+ try {
429
+ const { runSetup } = await import('./commands/setup.js');
430
+ const data = await runSetup({ reset: options.reset, set: options.set });
431
+ if (options.json) {
432
+ outputJson(data);
433
+ }
434
+ else if ('success' in data) {
435
+ // --set mode
436
+ for (const [key, value] of Object.entries(data.settings)) {
437
+ console.log(`\u2713 ${key}: ${value}`);
438
+ }
439
+ if (data.warnings) {
440
+ for (const w of data.warnings) {
441
+ console.warn(w);
442
+ }
443
+ }
444
+ }
445
+ else if ('setupComplete' in data && data.setupComplete) {
446
+ // Already complete
447
+ console.log('\n\u2699\ufe0f OSS Autopilot Setup\n');
448
+ console.log('\u2713 Setup already complete!\n');
449
+ console.log('Current settings:');
450
+ console.log(` GitHub username: ${data.config.githubUsername || '(not set)'}`);
451
+ console.log(` Max active PRs: ${data.config.maxActivePRs}`);
452
+ console.log(` Dormant threshold: ${data.config.dormantThresholdDays} days`);
453
+ console.log(` Approaching dormant: ${data.config.approachingDormantDays} days`);
454
+ console.log(` Languages: ${data.config.languages.join(', ')}`);
455
+ console.log(` Labels: ${data.config.labels.join(', ')}`);
456
+ console.log(`\nRun 'setup --reset' to reconfigure.`);
457
+ }
458
+ else if ('setupRequired' in data) {
459
+ // Needs setup
460
+ console.log('\n\u2699\ufe0f OSS Autopilot Setup\n');
461
+ console.log('SETUP_REQUIRED');
462
+ console.log('---');
463
+ console.log('Please configure the following settings:\n');
464
+ for (const prompt of data.prompts) {
465
+ console.log(`SETTING: ${prompt.setting}`);
466
+ console.log(`PROMPT: ${prompt.prompt}`);
467
+ const currentVal = Array.isArray(prompt.current) ? prompt.current.join(', ') : prompt.current;
468
+ console.log(`CURRENT: ${currentVal ?? '(not set)'}`);
469
+ if (prompt.required)
470
+ console.log('REQUIRED: true');
471
+ if (prompt.default !== undefined) {
472
+ const defaultVal = Array.isArray(prompt.default) ? prompt.default.join(', ') : prompt.default;
473
+ console.log(`DEFAULT: ${defaultVal}`);
474
+ }
475
+ if (prompt.type)
476
+ console.log(`TYPE: ${prompt.type}`);
477
+ console.log('');
478
+ }
479
+ console.log('---');
480
+ console.log('END_SETUP_PROMPTS');
481
+ }
482
+ }
483
+ catch (err) {
484
+ handleCommandError(err, options.json);
485
+ }
181
486
  });
182
487
  // Check setup command
183
488
  program
@@ -185,13 +490,42 @@ program
185
490
  .description('Check if setup is complete')
186
491
  .option('--json', 'Output as JSON')
187
492
  .action(async (options) => {
188
- const { runCheckSetup } = await import('./commands/setup.js');
189
- await runCheckSetup({ json: options.json });
493
+ try {
494
+ const { runCheckSetup } = await import('./commands/setup.js');
495
+ const data = await runCheckSetup();
496
+ if (options.json) {
497
+ outputJson(data);
498
+ }
499
+ else if (data.setupComplete) {
500
+ console.log('SETUP_COMPLETE');
501
+ console.log(`username=${data.username}`);
502
+ }
503
+ else {
504
+ console.log('SETUP_INCOMPLETE');
505
+ }
506
+ }
507
+ catch (err) {
508
+ handleCommandError(err, options.json);
509
+ }
510
+ });
511
+ // Dashboard commands
512
+ const dashboardCmd = program.command('dashboard').description('Dashboard commands');
513
+ dashboardCmd
514
+ .command('serve')
515
+ .description('Start interactive dashboard server')
516
+ .option('--port <port>', 'Port to listen on', '3000')
517
+ .option('--no-open', 'Do not open browser automatically')
518
+ .action(async (options) => {
519
+ const port = parseInt(options.port, 10);
520
+ if (isNaN(port) || port < 1 || port > 65535) {
521
+ console.error(`Invalid port number: "${options.port}". Must be an integer between 1 and 65535.`);
522
+ process.exit(1);
523
+ }
524
+ const { serveDashboard } = await import('./commands/dashboard.js');
525
+ await serveDashboard({ port, open: options.open });
190
526
  });
191
- // Dashboard command
192
- program
193
- .command('dashboard')
194
- .description('Generate HTML stats dashboard')
527
+ // Keep bare `dashboard` (no subcommand) for backward compat — generates static HTML
528
+ dashboardCmd
195
529
  .option('--open', 'Open in browser')
196
530
  .option('--json', 'Output as JSON')
197
531
  .option('--offline', 'Use cached data only (no GitHub API calls)')
@@ -205,8 +539,34 @@ program
205
539
  .description('Parse a markdown issue list into structured JSON')
206
540
  .option('--json', 'Output as JSON')
207
541
  .action(async (filePath, options) => {
208
- const { runParseList } = await import('./commands/parse-list.js');
209
- await runParseList({ filePath, json: options.json });
542
+ try {
543
+ const { runParseList } = await import('./commands/parse-list.js');
544
+ const data = await runParseList({ filePath });
545
+ if (options.json) {
546
+ outputJson(data);
547
+ }
548
+ else {
549
+ const path = await import('path');
550
+ const resolvedPath = path.resolve(filePath);
551
+ console.log(`\n\ud83d\udccb Issue List: ${resolvedPath}\n`);
552
+ console.log(`Available: ${data.availableCount} | Completed: ${data.completedCount}\n`);
553
+ if (data.available.length > 0) {
554
+ console.log('--- Available ---');
555
+ for (const item of data.available) {
556
+ console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
557
+ }
558
+ }
559
+ if (data.completed.length > 0) {
560
+ console.log('\n--- Completed ---');
561
+ for (const item of data.completed) {
562
+ console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
563
+ }
564
+ }
565
+ }
566
+ }
567
+ catch (err) {
568
+ handleCommandError(err, options.json);
569
+ }
210
570
  });
211
571
  // Check integration command (#83)
212
572
  program
@@ -215,8 +575,36 @@ program
215
575
  .option('--base <branch>', 'Base branch to compare against', 'main')
216
576
  .option('--json', 'Output as JSON')
217
577
  .action(async (options) => {
218
- const { runCheckIntegration } = await import('./commands/check-integration.js');
219
- await runCheckIntegration({ base: options.base, json: options.json });
578
+ try {
579
+ const { runCheckIntegration } = await import('./commands/check-integration.js');
580
+ const data = await runCheckIntegration({ base: options.base });
581
+ if (options.json) {
582
+ outputJson(data);
583
+ }
584
+ else if (data.newFiles.length === 0) {
585
+ console.log('\nNo new code files to check.');
586
+ }
587
+ else {
588
+ console.log(`\n\ud83d\udd0d Integration Check (base: ${options.base})\n`);
589
+ console.log(`New files: ${data.newFiles.length} | Unreferenced: ${data.unreferencedCount}\n`);
590
+ for (const file of data.newFiles) {
591
+ const status = file.isIntegrated ? '\u2705' : '\u26a0\ufe0f';
592
+ console.log(`${status} ${file.path}`);
593
+ if (file.isIntegrated) {
594
+ console.log(` Referenced by: ${file.referencedBy.join(', ')}`);
595
+ }
596
+ else {
597
+ console.log(' Not referenced by any file');
598
+ if (file.suggestedEntryPoints && file.suggestedEntryPoints.length > 0) {
599
+ console.log(` Suggested entry points: ${file.suggestedEntryPoints.join(', ')}`);
600
+ }
601
+ }
602
+ }
603
+ }
604
+ }
605
+ catch (err) {
606
+ handleCommandError(err, options.json);
607
+ }
220
608
  });
221
609
  // Local repos command (#84)
222
610
  program
@@ -226,8 +614,24 @@ program
226
614
  .option('--paths <dirs...>', 'Directories to scan')
227
615
  .option('--json', 'Output as JSON')
228
616
  .action(async (options) => {
229
- const { runLocalRepos } = await import('./commands/local-repos.js');
230
- await runLocalRepos({ scan: options.scan, paths: options.paths, json: options.json });
617
+ try {
618
+ const { runLocalRepos } = await import('./commands/local-repos.js');
619
+ const data = await runLocalRepos({ scan: options.scan, paths: options.paths });
620
+ if (options.json) {
621
+ outputJson(data);
622
+ }
623
+ else if (data.fromCache) {
624
+ console.log(`\n\ud83d\udcc1 Local Repos (cached ${data.cachedAt})\n`);
625
+ printRepos(data.repos);
626
+ }
627
+ else {
628
+ console.log(`Found ${Object.keys(data.repos).length} repos:\n`);
629
+ printRepos(data.repos);
630
+ }
631
+ }
632
+ catch (err) {
633
+ handleCommandError(err, options.json);
634
+ }
231
635
  });
232
636
  // Startup command (combines auth, setup, daily, dashboard, issue list)
233
637
  program
@@ -235,8 +639,30 @@ program
235
639
  .description('Run all pre-flight checks and daily fetch in one call')
236
640
  .option('--json', 'Output as JSON')
237
641
  .action(async (options) => {
238
- const { runStartup } = await import('./commands/startup.js');
239
- await runStartup({ json: options.json });
642
+ try {
643
+ const { runStartup } = await import('./commands/startup.js');
644
+ const data = await runStartup();
645
+ if (options.json) {
646
+ outputJson(data);
647
+ }
648
+ else {
649
+ if (!data.setupComplete) {
650
+ console.log('Setup incomplete. Run /setup-oss first.');
651
+ }
652
+ else if (data.authError) {
653
+ console.error(`Error: ${data.authError}`);
654
+ }
655
+ else {
656
+ console.log(`OSS Autopilot v${data.version}`);
657
+ console.log(data.daily?.briefSummary ?? '');
658
+ if (data.dashboardPath)
659
+ console.log(`Dashboard: ${data.dashboardPath}`);
660
+ }
661
+ }
662
+ }
663
+ catch (err) {
664
+ handleCommandError(err, options.json);
665
+ }
240
666
  });
241
667
  // Shelve command
242
668
  program
@@ -244,8 +670,24 @@ program
244
670
  .description('Shelve a PR (exclude from capacity and actionable issues)')
245
671
  .option('--json', 'Output as JSON')
246
672
  .action(async (prUrl, options) => {
247
- const { runShelve } = await import('./commands/shelve.js');
248
- await runShelve({ prUrl, json: options.json });
673
+ try {
674
+ const { runShelve } = await import('./commands/shelve.js');
675
+ const data = await runShelve({ prUrl });
676
+ if (options.json) {
677
+ outputJson(data);
678
+ }
679
+ else if (data.shelved) {
680
+ console.log(`Shelved: ${prUrl}`);
681
+ console.log('This PR is now excluded from capacity and actionable issues.');
682
+ console.log('It will auto-unshelve if a maintainer engages.');
683
+ }
684
+ else {
685
+ console.log('PR is already shelved.');
686
+ }
687
+ }
688
+ catch (err) {
689
+ handleCommandError(err, options.json);
690
+ }
249
691
  });
250
692
  // Unshelve command
251
693
  program
@@ -253,8 +695,23 @@ program
253
695
  .description('Unshelve a PR (include in capacity and actionable issues again)')
254
696
  .option('--json', 'Output as JSON')
255
697
  .action(async (prUrl, options) => {
256
- const { runUnshelve } = await import('./commands/shelve.js');
257
- await runUnshelve({ prUrl, json: options.json });
698
+ try {
699
+ const { runUnshelve } = await import('./commands/shelve.js');
700
+ const data = await runUnshelve({ prUrl });
701
+ if (options.json) {
702
+ outputJson(data);
703
+ }
704
+ else if (data.unshelved) {
705
+ console.log(`Unshelved: ${prUrl}`);
706
+ console.log('This PR is now active again.');
707
+ }
708
+ else {
709
+ console.log('PR was not shelved.');
710
+ }
711
+ }
712
+ catch (err) {
713
+ handleCommandError(err, options.json);
714
+ }
258
715
  });
259
716
  // Dismiss command
260
717
  program
@@ -262,8 +719,24 @@ program
262
719
  .description('Dismiss issue reply notifications (resurfaces on new activity)')
263
720
  .option('--json', 'Output as JSON')
264
721
  .action(async (issueUrl, options) => {
265
- const { runDismiss } = await import('./commands/dismiss.js');
266
- await runDismiss({ issueUrl, json: options.json });
722
+ try {
723
+ const { runDismiss } = await import('./commands/dismiss.js');
724
+ const data = await runDismiss({ issueUrl });
725
+ if (options.json) {
726
+ outputJson(data);
727
+ }
728
+ else if (data.dismissed) {
729
+ console.log(`Dismissed: ${issueUrl}`);
730
+ console.log('Issue reply notifications are now muted.');
731
+ console.log('New responses after this point will resurface automatically.');
732
+ }
733
+ else {
734
+ console.log('Issue is already dismissed.');
735
+ }
736
+ }
737
+ catch (err) {
738
+ handleCommandError(err, options.json);
739
+ }
267
740
  });
268
741
  // Undismiss command
269
742
  program
@@ -271,8 +744,23 @@ program
271
744
  .description('Undismiss an issue (re-enable reply notifications)')
272
745
  .option('--json', 'Output as JSON')
273
746
  .action(async (issueUrl, options) => {
274
- const { runUndismiss } = await import('./commands/dismiss.js');
275
- await runUndismiss({ issueUrl, json: options.json });
747
+ try {
748
+ const { runUndismiss } = await import('./commands/dismiss.js');
749
+ const data = await runUndismiss({ issueUrl });
750
+ if (options.json) {
751
+ outputJson(data);
752
+ }
753
+ else if (data.undismissed) {
754
+ console.log(`Undismissed: ${issueUrl}`);
755
+ console.log('Issue reply notifications are active again.');
756
+ }
757
+ else {
758
+ console.log('Issue was not dismissed.');
759
+ }
760
+ }
761
+ catch (err) {
762
+ handleCommandError(err, options.json);
763
+ }
276
764
  });
277
765
  // Snooze command
278
766
  program
@@ -282,8 +770,29 @@ program
282
770
  .option('--days <days>', 'Number of days to snooze (default: 7)', '7')
283
771
  .option('--json', 'Output as JSON')
284
772
  .action(async (prUrl, options) => {
285
- const { runSnooze } = await import('./commands/snooze.js');
286
- await runSnooze({ prUrl, reason: options.reason, days: parseInt(options.days, 10), json: options.json });
773
+ try {
774
+ const { runSnooze } = await import('./commands/snooze.js');
775
+ const data = await runSnooze({ prUrl, reason: options.reason, days: parseInt(options.days, 10) });
776
+ if (options.json) {
777
+ outputJson(data);
778
+ }
779
+ else if (data.snoozed) {
780
+ console.log(`Snoozed: ${prUrl}`);
781
+ console.log(`Reason: ${data.reason}`);
782
+ console.log(`Duration: ${data.days} day${data.days === 1 ? '' : 's'}`);
783
+ console.log(`Expires: ${data.expiresAt ? new Date(data.expiresAt).toLocaleString() : 'unknown'}`);
784
+ console.log('CI failure notifications are now muted for this PR.');
785
+ }
786
+ else {
787
+ console.log('PR is already snoozed.');
788
+ if (data.expiresAt) {
789
+ console.log(`Expires: ${new Date(data.expiresAt).toLocaleString()}`);
790
+ }
791
+ }
792
+ }
793
+ catch (err) {
794
+ handleCommandError(err, options.json);
795
+ }
287
796
  });
288
797
  // Unsnooze command
289
798
  program
@@ -291,8 +800,23 @@ program
291
800
  .description('Unsnooze a PR (re-enable CI failure notifications)')
292
801
  .option('--json', 'Output as JSON')
293
802
  .action(async (prUrl, options) => {
294
- const { runUnsnooze } = await import('./commands/snooze.js');
295
- await runUnsnooze({ prUrl, json: options.json });
803
+ try {
804
+ const { runUnsnooze } = await import('./commands/snooze.js');
805
+ const data = await runUnsnooze({ prUrl });
806
+ if (options.json) {
807
+ outputJson(data);
808
+ }
809
+ else if (data.unsnoozed) {
810
+ console.log(`Unsnoozed: ${prUrl}`);
811
+ console.log('CI failure notifications are active again for this PR.');
812
+ }
813
+ else {
814
+ console.log('PR was not snoozed.');
815
+ }
816
+ }
817
+ catch (err) {
818
+ handleCommandError(err, options.json);
819
+ }
296
820
  });
297
821
  // Validate GitHub token before running commands that need it
298
822
  program.hook('preAction', async (thisCommand, actionCommand) => {