@oss-autopilot/core 0.41.0 → 0.42.0

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