@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.
- package/dist/cli.bundle.cjs +1300 -1020
- package/dist/cli.js +591 -55
- package/dist/commands/check-integration.d.ts +3 -3
- package/dist/commands/check-integration.js +5 -39
- package/dist/commands/comments.d.ts +6 -9
- package/dist/commands/comments.js +78 -228
- package/dist/commands/config.d.ts +8 -2
- package/dist/commands/config.js +6 -28
- package/dist/commands/daily.d.ts +28 -4
- package/dist/commands/daily.js +19 -32
- package/dist/commands/dashboard-server.d.ts +14 -0
- package/dist/commands/dashboard-server.js +361 -0
- package/dist/commands/dashboard.d.ts +5 -0
- package/dist/commands/dashboard.js +49 -0
- package/dist/commands/dismiss.d.ts +13 -5
- package/dist/commands/dismiss.js +4 -24
- package/dist/commands/index.d.ts +33 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +4 -14
- package/dist/commands/local-repos.d.ts +4 -5
- package/dist/commands/local-repos.js +4 -32
- package/dist/commands/parse-list.d.ts +3 -4
- package/dist/commands/parse-list.js +6 -38
- package/dist/commands/read.d.ts +11 -5
- package/dist/commands/read.js +4 -18
- package/dist/commands/search.d.ts +3 -3
- package/dist/commands/search.js +39 -65
- package/dist/commands/setup.d.ts +34 -5
- package/dist/commands/setup.js +75 -166
- package/dist/commands/shelve.d.ts +13 -5
- package/dist/commands/shelve.js +4 -24
- package/dist/commands/snooze.d.ts +15 -9
- package/dist/commands/snooze.js +16 -59
- package/dist/commands/startup.d.ts +11 -6
- package/dist/commands/startup.js +39 -67
- package/dist/commands/status.d.ts +3 -3
- package/dist/commands/status.js +10 -29
- package/dist/commands/track.d.ts +10 -9
- package/dist/commands/track.js +17 -39
- package/dist/commands/validation.d.ts +2 -2
- package/dist/commands/validation.js +3 -11
- package/dist/commands/vet.d.ts +3 -3
- package/dist/commands/vet.js +16 -26
- package/dist/formatters/json.d.ts +58 -0
- 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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
//
|
|
192
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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) => {
|