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