gitpadi 2.0.1 → 2.0.3
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/README.md +57 -225
- package/dist/cli.js +279 -146
- package/dist/commands/contribute.js +314 -0
- package/dist/commands/issues.js +107 -24
- package/dist/commands/repos.js +31 -21
- package/dist/core/github.js +47 -2
- package/package.json +2 -2
- package/src/cli.ts +312 -174
- package/src/commands/contribute.ts +354 -0
- package/src/commands/issues.ts +118 -26
- package/src/commands/repos.ts +41 -26
- package/src/core/github.ts +54 -2
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as prs from './commands/prs.js';
|
|
|
17
17
|
import * as repos from './commands/repos.js';
|
|
18
18
|
import * as contributors from './commands/contributors.js';
|
|
19
19
|
import * as releases from './commands/releases.js';
|
|
20
|
+
import * as contribute from './commands/contribute.js';
|
|
20
21
|
const VERSION = '2.0.0';
|
|
21
22
|
// ── Styling ────────────────────────────────────────────────────────────
|
|
22
23
|
const cyber = gradient(['#ff00ff', '#00ffff', '#ff00ff']);
|
|
@@ -108,33 +109,28 @@ async function bootSequence() {
|
|
|
108
109
|
console.log('');
|
|
109
110
|
}
|
|
110
111
|
// ── Onboarding ─────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Ensures we have a valid GitHub token
|
|
114
|
+
*/
|
|
115
|
+
async function ensureAuthenticated() {
|
|
113
116
|
initGitHub();
|
|
114
117
|
let token = getToken();
|
|
115
|
-
|
|
116
|
-
let repo = getRepo();
|
|
117
|
-
// If everything is already set (likely via env or previously saved config), skip
|
|
118
|
-
if (token && owner && repo) {
|
|
119
|
-
const spinner = createSpinner(dim('Authenticating...')).start();
|
|
118
|
+
if (token) {
|
|
120
119
|
try {
|
|
121
120
|
const octokit = getOctokit();
|
|
122
121
|
await octokit.users.getAuthenticated();
|
|
123
|
-
|
|
124
|
-
console.log('');
|
|
122
|
+
// Silent success if token is valid
|
|
125
123
|
return;
|
|
126
124
|
}
|
|
127
125
|
catch {
|
|
128
|
-
|
|
129
|
-
// Clear and continue to prompt
|
|
126
|
+
console.log(red(' ❌ Saved session invalid. Re-authenticating...'));
|
|
130
127
|
token = '';
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
console.log(neon(' ⚡
|
|
134
|
-
const
|
|
135
|
-
{
|
|
130
|
+
console.log(neon(' ⚡ Authentication — let\'s connect you to GitHub.\n'));
|
|
131
|
+
const { t } = await inquirer.prompt([{
|
|
136
132
|
type: 'password',
|
|
137
|
-
name: '
|
|
133
|
+
name: 't',
|
|
138
134
|
message: magenta('🔑 GitHub Token') + dim(' (ghp_xxx):'),
|
|
139
135
|
mask: '•',
|
|
140
136
|
validate: async (v) => {
|
|
@@ -153,15 +149,27 @@ async function onboarding() {
|
|
|
153
149
|
return 'Invalid token';
|
|
154
150
|
}
|
|
155
151
|
},
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
}]);
|
|
153
|
+
initGitHub(t);
|
|
154
|
+
saveConfig({ token: t, owner: getOwner() || '', repo: getRepo() || '' });
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Ensures we have a target repository (Owner/Repo)
|
|
158
|
+
*/
|
|
159
|
+
async function ensureTargetRepo() {
|
|
160
|
+
let owner = getOwner();
|
|
161
|
+
let repo = getRepo();
|
|
162
|
+
if (owner && repo) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
|
|
166
|
+
const answers = await inquirer.prompt([
|
|
158
167
|
{
|
|
159
168
|
type: 'input',
|
|
160
169
|
name: 'owner',
|
|
161
170
|
message: cyan('👤 GitHub Owner/Org:'),
|
|
162
171
|
default: owner || '',
|
|
163
172
|
validate: (v) => v.length > 0 || 'Required',
|
|
164
|
-
when: !owner,
|
|
165
173
|
},
|
|
166
174
|
{
|
|
167
175
|
type: 'input',
|
|
@@ -169,70 +177,126 @@ async function onboarding() {
|
|
|
169
177
|
message: cyan('📦 Repository name:'),
|
|
170
178
|
default: repo || '',
|
|
171
179
|
validate: (v) => v.length > 0 || 'Required',
|
|
172
|
-
when: !repo,
|
|
173
180
|
},
|
|
174
181
|
]);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
initGitHub(finalToken, finalOwner, finalRepo);
|
|
179
|
-
saveConfig({ token: finalToken, owner: finalOwner, repo: finalRepo });
|
|
180
|
-
const spinner = createSpinner(dim('Connecting to GitHub...')).start();
|
|
182
|
+
setRepo(answers.owner, answers.repo);
|
|
183
|
+
saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
|
|
184
|
+
const spinner = createSpinner(dim('Connecting...')).start();
|
|
181
185
|
await sleep(400);
|
|
182
|
-
spinner.success({ text: green(`Locked in → ${cyan(`${
|
|
183
|
-
console.log('');
|
|
184
|
-
console.log(boxen(dim('💡 Session saved to ~/.gitpadi/config.json\n\n') +
|
|
185
|
-
dim('Set environment variables to override:\n') +
|
|
186
|
-
yellow(' export GITHUB_TOKEN=ghp_xxx\n') +
|
|
187
|
-
yellow(' export GITHUB_OWNER=' + finalOwner + '\n') +
|
|
188
|
-
yellow(' export GITHUB_REPO=' + finalRepo), { padding: 1, borderColor: 'yellow', dimBorder: true, borderStyle: 'round' }));
|
|
186
|
+
spinner.success({ text: green(`Locked in → ${cyan(`${answers.owner}/${answers.repo}`)}`) });
|
|
189
187
|
console.log('');
|
|
190
188
|
}
|
|
191
|
-
// ──
|
|
189
|
+
// ── Mode Selector ──────────────────────────────────────────────────────
|
|
192
190
|
async function mainMenu() {
|
|
193
191
|
while (true) {
|
|
194
192
|
line('═');
|
|
195
|
-
console.log(cyber(' ⟨ GITPADI
|
|
196
|
-
console.log(dim(' Select
|
|
193
|
+
console.log(cyber(' ⟨ GITPADI MODE SELECTOR ⟩'));
|
|
194
|
+
console.log(dim(' Select your workflow persona to continue'));
|
|
197
195
|
line('═');
|
|
198
196
|
console.log('');
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}]);
|
|
217
|
-
category = ans.category;
|
|
197
|
+
const { mode } = await inquirer.prompt([{
|
|
198
|
+
type: 'list',
|
|
199
|
+
name: 'mode',
|
|
200
|
+
message: bold('Choose your path:'),
|
|
201
|
+
choices: [
|
|
202
|
+
{ name: `${cyan('✨')} ${bold('Contributor Mode')} ${dim('— fork, clone, sync, submit PRs')}`, value: 'contributor' },
|
|
203
|
+
{ name: `${magenta('🛠️')} ${bold('Maintainer Mode')} ${dim('— manage issues, PRs, contributors')}`, value: 'maintainer' },
|
|
204
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
205
|
+
{ name: `${dim('👋')} ${dim('Exit')}`, value: 'exit' },
|
|
206
|
+
],
|
|
207
|
+
loop: false,
|
|
208
|
+
}]);
|
|
209
|
+
if (mode === 'contributor')
|
|
210
|
+
await safeMenu(contributorMenu);
|
|
211
|
+
else if (mode === 'maintainer') {
|
|
212
|
+
await ensureTargetRepo();
|
|
213
|
+
await safeMenu(maintainerMenu);
|
|
218
214
|
}
|
|
219
|
-
|
|
220
|
-
// Ctrl+C on main menu = exit
|
|
221
|
-
console.log('');
|
|
222
|
-
console.log(dim(' ▸ Saving session...'));
|
|
223
|
-
console.log(dim(' ▸ Disconnecting from GitHub...'));
|
|
224
|
-
console.log('');
|
|
225
|
-
console.log(fire(' 🤖 GitPadi is shutting down now. See you soon, pal :)\n'));
|
|
215
|
+
else
|
|
226
216
|
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── Contributor Menu ───────────────────────────────────────────────────
|
|
220
|
+
async function contributorMenu() {
|
|
221
|
+
while (true) {
|
|
222
|
+
line('═');
|
|
223
|
+
console.log(cyan(' ✨ GITPADI CONTRIBUTOR WORKSPACE'));
|
|
224
|
+
console.log(dim(' Automating forking, syncing, and PR delivery'));
|
|
225
|
+
line('═');
|
|
226
|
+
// Auto-check for updates if in a repo
|
|
227
|
+
if (getOwner() && getRepo()) {
|
|
228
|
+
await contribute.syncBranch();
|
|
227
229
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
const { action } = await inquirer.prompt([{
|
|
231
|
+
type: 'list',
|
|
232
|
+
name: 'action',
|
|
233
|
+
message: bold('Contributor Action:'),
|
|
234
|
+
choices: [
|
|
235
|
+
{ name: `${cyan('🚀')} ${bold('Start Contribution')} ${dim('— fork, clone & branch')}`, value: 'start' },
|
|
236
|
+
{ name: `${green('🔄')} ${bold('Sync with Upstream')} ${dim('— pull latest changes')}`, value: 'sync' },
|
|
237
|
+
{ name: `${yellow('📋')} ${bold('View Action Logs')} ${dim('— check PR/commit status')}`, value: 'logs' },
|
|
238
|
+
{ name: `${magenta('🚀')} ${bold('Submit PR')} ${dim('— add, commit, push & PR')}`, value: 'submit' },
|
|
239
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
240
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
241
|
+
],
|
|
242
|
+
loop: false,
|
|
243
|
+
}]);
|
|
244
|
+
if (action === 'back')
|
|
234
245
|
break;
|
|
246
|
+
if (action === 'start') {
|
|
247
|
+
const { url } = await ask([{ type: 'input', name: 'url', message: 'Enter Repo or Issue URL:' }]);
|
|
248
|
+
await contribute.forkAndClone(url);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// These actions require a target repo
|
|
252
|
+
await ensureTargetRepo();
|
|
253
|
+
if (action === 'sync') {
|
|
254
|
+
await contribute.syncBranch();
|
|
255
|
+
}
|
|
256
|
+
else if (action === 'logs') {
|
|
257
|
+
await contribute.viewLogs();
|
|
258
|
+
}
|
|
259
|
+
else if (action === 'submit') {
|
|
260
|
+
const { title, message, issue } = await ask([
|
|
261
|
+
{ type: 'input', name: 'title', message: 'PR Title:' },
|
|
262
|
+
{ type: 'input', name: 'message', message: 'Commit message (optional):' },
|
|
263
|
+
{ type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
|
|
264
|
+
]);
|
|
265
|
+
await contribute.submitPR({
|
|
266
|
+
title,
|
|
267
|
+
message: message || title,
|
|
268
|
+
issue: issue ? parseInt(issue) : undefined
|
|
269
|
+
});
|
|
270
|
+
}
|
|
235
271
|
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// ── Maintainer Menu ────────────────────────────────────────────────────
|
|
275
|
+
async function maintainerMenu() {
|
|
276
|
+
while (true) {
|
|
277
|
+
line('═');
|
|
278
|
+
console.log(magenta(' 🛠️ GITPADI MAINTAINER PANEL'));
|
|
279
|
+
console.log(dim(' Managing repository health and contributor intake'));
|
|
280
|
+
line('═');
|
|
281
|
+
console.log('');
|
|
282
|
+
const { category } = await inquirer.prompt([{
|
|
283
|
+
type: 'list',
|
|
284
|
+
name: 'category',
|
|
285
|
+
message: bold('Select operation:'),
|
|
286
|
+
choices: [
|
|
287
|
+
{ name: `${magenta('📋')} ${bold('Issues')} ${dim('— create, close, delete, assign, search')}`, value: 'issues' },
|
|
288
|
+
{ name: `${cyan('🔀')} ${bold('Pull Requests')} ${dim('— merge, review, approve, diff')}`, value: 'prs' },
|
|
289
|
+
{ name: `${green('📦')} ${bold('Repositories')} ${dim('— create, delete, clone, info')}`, value: 'repos' },
|
|
290
|
+
{ name: `${yellow('🏆')} ${bold('Contributors')} ${dim('— score, rank, auto-assign best')}`, value: 'contributors' },
|
|
291
|
+
{ name: `${red('🚀')} ${bold('Releases')} ${dim('— create, list, manage')}`, value: 'releases' },
|
|
292
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
293
|
+
{ name: `${dim('⚙️')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
294
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
295
|
+
],
|
|
296
|
+
loop: false,
|
|
297
|
+
}]);
|
|
298
|
+
if (category === 'back')
|
|
299
|
+
break;
|
|
236
300
|
if (category === 'switch') {
|
|
237
301
|
await safeMenu(async () => {
|
|
238
302
|
const a = await inquirer.prompt([
|
|
@@ -248,13 +312,13 @@ async function mainMenu() {
|
|
|
248
312
|
}
|
|
249
313
|
if (category === 'issues')
|
|
250
314
|
await safeMenu(issueMenu);
|
|
251
|
-
if (category === 'prs')
|
|
315
|
+
else if (category === 'prs')
|
|
252
316
|
await safeMenu(prMenu);
|
|
253
|
-
if (category === 'repos')
|
|
317
|
+
else if (category === 'repos')
|
|
254
318
|
await safeMenu(repoMenu);
|
|
255
|
-
if (category === 'contributors')
|
|
256
|
-
await safeMenu(
|
|
257
|
-
if (category === 'releases')
|
|
319
|
+
else if (category === 'contributors')
|
|
320
|
+
await safeMenu(contributorScoringMenu);
|
|
321
|
+
else if (category === 'releases')
|
|
258
322
|
await safeMenu(releaseMenu);
|
|
259
323
|
console.log('');
|
|
260
324
|
}
|
|
@@ -264,11 +328,11 @@ async function issueMenu() {
|
|
|
264
328
|
const { action } = await inquirer.prompt([{
|
|
265
329
|
type: 'list', name: 'action', message: magenta('📋 Issue Operation:'),
|
|
266
330
|
choices: [
|
|
267
|
-
{ name: ` ${dim('⬅ Back
|
|
331
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
268
332
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
269
333
|
{ name: ` ${green('▸')} List open issues`, value: 'list' },
|
|
270
334
|
{ name: ` ${green('▸')} Create single issue`, value: 'create' },
|
|
271
|
-
{ name: ` ${magenta('▸')} Bulk create from JSON
|
|
335
|
+
{ name: ` ${magenta('▸')} Bulk create from file (JSON/MD)`, value: 'bulk' },
|
|
272
336
|
{ name: ` ${red('▸')} Close issue`, value: 'close' },
|
|
273
337
|
{ name: ` ${green('▸')} Reopen issue`, value: 'reopen' },
|
|
274
338
|
{ name: ` ${red('▸')} Delete (close & lock)`, value: 'delete' },
|
|
@@ -298,27 +362,19 @@ async function issueMenu() {
|
|
|
298
362
|
}
|
|
299
363
|
else if (action === 'bulk') {
|
|
300
364
|
const a = await ask([
|
|
301
|
-
{ type: 'input', name: 'file', message: yellow('📁 Path to issues JSON
|
|
302
|
-
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
365
|
+
{ type: 'input', name: 'file', message: yellow('📁 Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
303
366
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
304
367
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
305
368
|
]);
|
|
306
|
-
await issues.createIssuesFromFile(a.file, {
|
|
369
|
+
await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
|
|
307
370
|
}
|
|
308
371
|
else if (action === 'assign-best') {
|
|
309
|
-
// ── Smart Auto-Assign Flow ──
|
|
310
|
-
// 1. Fetch all open issues with comments
|
|
311
|
-
// 2. Filter to ones with applicant comments
|
|
312
|
-
// 3. Let user pick from a list
|
|
313
|
-
// 4. Show applicants + scores, then assign best
|
|
314
372
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
315
373
|
const octokit = getOctokit();
|
|
316
374
|
const { data: allIssues } = await octokit.issues.listForRepo({
|
|
317
375
|
owner: getOwner(), repo: getRepo(), state: 'open', per_page: 50,
|
|
318
376
|
});
|
|
319
|
-
// Filter to real issues (not PRs) with comments
|
|
320
377
|
const realIssues = allIssues.filter((i) => !i.pull_request && i.comments > 0);
|
|
321
|
-
// Check each issue for applicant comments
|
|
322
378
|
const issuesWithApplicants = [];
|
|
323
379
|
for (const issue of realIssues) {
|
|
324
380
|
const { data: comments } = await octokit.issues.listComments({
|
|
@@ -345,7 +401,6 @@ async function issueMenu() {
|
|
|
345
401
|
return;
|
|
346
402
|
}
|
|
347
403
|
console.log(`\n${bold(`🏆 Issues with Applicants`)} (${issuesWithApplicants.length})\n`);
|
|
348
|
-
// Let user pick an issue
|
|
349
404
|
const { picked } = await inquirer.prompt([{
|
|
350
405
|
type: 'list', name: 'picked', message: yellow('Select an issue:'),
|
|
351
406
|
choices: [
|
|
@@ -358,7 +413,6 @@ async function issueMenu() {
|
|
|
358
413
|
}]);
|
|
359
414
|
if (picked === -1)
|
|
360
415
|
return;
|
|
361
|
-
// Run the scoring
|
|
362
416
|
await issues.assignBest(picked);
|
|
363
417
|
}
|
|
364
418
|
else if (action === 'close' || action === 'reopen' || action === 'delete') {
|
|
@@ -399,7 +453,7 @@ async function prMenu() {
|
|
|
399
453
|
const { action } = await inquirer.prompt([{
|
|
400
454
|
type: 'list', name: 'action', message: cyan('🔀 PR Operation:'),
|
|
401
455
|
choices: [
|
|
402
|
-
{ name: ` ${dim('⬅ Back
|
|
456
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
403
457
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
404
458
|
{ name: ` ${green('▸')} List pull requests`, value: 'list' },
|
|
405
459
|
{ name: ` ${green('▸')} Merge PR ${dim('(checks CI first)')}`, value: 'merge' },
|
|
@@ -451,7 +505,7 @@ async function repoMenu() {
|
|
|
451
505
|
const { action } = await inquirer.prompt([{
|
|
452
506
|
type: 'list', name: 'action', message: green('📦 Repo Operation:'),
|
|
453
507
|
choices: [
|
|
454
|
-
{ name: ` ${dim('⬅ Back
|
|
508
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
455
509
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
456
510
|
{ name: ` ${green('▸')} List repositories`, value: 'list' },
|
|
457
511
|
{ name: ` ${green('▸')} Create repo`, value: 'create' },
|
|
@@ -480,29 +534,89 @@ async function repoMenu() {
|
|
|
480
534
|
await repos.createRepo(a.name, { org: a.org || undefined, description: a.description, private: a.isPrivate });
|
|
481
535
|
}
|
|
482
536
|
else if (action === 'delete') {
|
|
483
|
-
const
|
|
484
|
-
{ type: 'input', name: '
|
|
485
|
-
{ type: 'input', name: 'org', message: 'Org:', default: getOwner() },
|
|
537
|
+
const { org } = await ask([
|
|
538
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
486
539
|
]);
|
|
540
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
541
|
+
let repoToDelete;
|
|
542
|
+
if (fetchedRepos.length > 0) {
|
|
543
|
+
const { selection } = await inquirer.prompt([{
|
|
544
|
+
type: 'list',
|
|
545
|
+
name: 'selection',
|
|
546
|
+
message: red('📦 Select Repo to DELETE:'),
|
|
547
|
+
choices: [
|
|
548
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
549
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
550
|
+
]
|
|
551
|
+
}]);
|
|
552
|
+
if (selection === 'back')
|
|
553
|
+
return;
|
|
554
|
+
repoToDelete = selection;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: red('📦 Repo name to DELETE:') }]);
|
|
558
|
+
repoToDelete = name;
|
|
559
|
+
}
|
|
487
560
|
const { confirm } = await ask([
|
|
488
|
-
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${
|
|
561
|
+
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${repoToDelete}" to confirm deletion:`), validate: (v) => v === repoToDelete || 'Name doesn\'t match' },
|
|
489
562
|
]);
|
|
490
|
-
if (confirm ===
|
|
491
|
-
await repos.deleteRepo(
|
|
563
|
+
if (confirm === repoToDelete)
|
|
564
|
+
await repos.deleteRepo(repoToDelete, { org });
|
|
492
565
|
}
|
|
493
566
|
else if (action === 'clone') {
|
|
494
|
-
const
|
|
495
|
-
{ type: 'input', name: '
|
|
496
|
-
|
|
567
|
+
const { org } = await ask([
|
|
568
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
569
|
+
]);
|
|
570
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
571
|
+
let repoToClone;
|
|
572
|
+
if (fetchedRepos.length > 0) {
|
|
573
|
+
const { selection } = await inquirer.prompt([{
|
|
574
|
+
type: 'list',
|
|
575
|
+
name: 'selection',
|
|
576
|
+
message: cyan('📦 Select Repo to Clone:'),
|
|
577
|
+
choices: [
|
|
578
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
579
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
580
|
+
]
|
|
581
|
+
}]);
|
|
582
|
+
if (selection === 'back')
|
|
583
|
+
return;
|
|
584
|
+
repoToClone = selection;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name to Clone:') }]);
|
|
588
|
+
repoToClone = name;
|
|
589
|
+
}
|
|
590
|
+
const { dir } = await ask([
|
|
591
|
+
{ type: 'input', name: 'dir', message: dim('Destination path (leave blank for default):'), default: '' },
|
|
497
592
|
]);
|
|
498
|
-
await repos.cloneRepo(
|
|
593
|
+
await repos.cloneRepo(repoToClone, { org, dir: dir || undefined });
|
|
499
594
|
}
|
|
500
595
|
else if (action === 'info') {
|
|
501
|
-
const
|
|
502
|
-
{ type: 'input', name: '
|
|
503
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
596
|
+
const { org } = await ask([
|
|
597
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
504
598
|
]);
|
|
505
|
-
await repos.
|
|
599
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
600
|
+
let repoToInfo;
|
|
601
|
+
if (fetchedRepos.length > 0) {
|
|
602
|
+
const { selection } = await inquirer.prompt([{
|
|
603
|
+
type: 'list',
|
|
604
|
+
name: 'selection',
|
|
605
|
+
message: cyan('📦 Select Repo for Info:'),
|
|
606
|
+
choices: [
|
|
607
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
608
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
609
|
+
]
|
|
610
|
+
}]);
|
|
611
|
+
if (selection === 'back')
|
|
612
|
+
return;
|
|
613
|
+
repoToInfo = selection;
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name:') }]);
|
|
617
|
+
repoToInfo = name;
|
|
618
|
+
}
|
|
619
|
+
await repos.repoInfo(repoToInfo, { org });
|
|
506
620
|
}
|
|
507
621
|
else if (action === 'topics') {
|
|
508
622
|
const a = await ask([
|
|
@@ -512,12 +626,12 @@ async function repoMenu() {
|
|
|
512
626
|
await repos.setTopics(a.name, a.topics.split(/\s+/), { org: getOwner() });
|
|
513
627
|
}
|
|
514
628
|
}
|
|
515
|
-
// ── Contributor Menu
|
|
516
|
-
async function
|
|
629
|
+
// ── Contributor Scoring Menu (Maintainer Tool) ─────────────────────────
|
|
630
|
+
async function contributorScoringMenu() {
|
|
517
631
|
const { action } = await inquirer.prompt([{
|
|
518
632
|
type: 'list', name: 'action', message: yellow('🏆 Contributor Operation:'),
|
|
519
633
|
choices: [
|
|
520
|
-
{ name: ` ${dim('⬅ Back
|
|
634
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
521
635
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
522
636
|
{ name: ` ${yellow('▸')} Score a user`, value: 'score' },
|
|
523
637
|
{ name: ` ${yellow('▸')} Rank applicants for issue`, value: 'rank' },
|
|
@@ -534,7 +648,7 @@ async function contributorMenu() {
|
|
|
534
648
|
const { n } = await ask([{ type: 'input', name: 'n', message: yellow('Issue #') + dim(' (q=back):') }]);
|
|
535
649
|
await contributors.rankApplicants(parseInt(n));
|
|
536
650
|
}
|
|
537
|
-
else {
|
|
651
|
+
else if (action === 'list') {
|
|
538
652
|
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
539
653
|
await contributors.listContributors({ limit: parseInt(a.limit) });
|
|
540
654
|
}
|
|
@@ -544,26 +658,27 @@ async function releaseMenu() {
|
|
|
544
658
|
const { action } = await inquirer.prompt([{
|
|
545
659
|
type: 'list', name: 'action', message: red('🚀 Release Operation:'),
|
|
546
660
|
choices: [
|
|
547
|
-
{ name: ` ${dim('⬅ Back
|
|
661
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
548
662
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
549
|
-
{ name: ` ${
|
|
550
|
-
{ name: ` ${
|
|
663
|
+
{ name: ` ${green('▸')} List releases`, value: 'list' },
|
|
664
|
+
{ name: ` ${green('▸')} Create release`, value: 'create' },
|
|
551
665
|
],
|
|
552
666
|
}]);
|
|
553
667
|
if (action === 'back')
|
|
554
668
|
return;
|
|
555
|
-
if (action === '
|
|
669
|
+
if (action === 'list') {
|
|
670
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' }]);
|
|
671
|
+
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
672
|
+
}
|
|
673
|
+
else if (action === 'create') {
|
|
556
674
|
const a = await ask([
|
|
557
|
-
{ type: 'input', name: 'tag', message: yellow('Tag (e.g
|
|
558
|
-
{ type: 'input', name: 'name', message: dim('Release name:'), default: '' },
|
|
675
|
+
{ type: 'input', name: 'tag', message: yellow('Tag (e.g. v1.0.0):') },
|
|
676
|
+
{ type: 'input', name: 'name', message: dim('Release name (optional):'), default: '' },
|
|
677
|
+
{ type: 'input', name: 'body', message: dim('Release body (optional):'), default: '' },
|
|
559
678
|
{ type: 'confirm', name: 'draft', message: 'Draft?', default: false },
|
|
560
|
-
{ type: 'confirm', name: 'prerelease', message: '
|
|
679
|
+
{ type: 'confirm', name: 'prerelease', message: 'Prerelease?', default: false },
|
|
561
680
|
]);
|
|
562
|
-
await releases.createRelease(a.tag, { name: a.name
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
566
|
-
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
681
|
+
await releases.createRelease(a.tag, { name: a.name, body: a.body, draft: a.draft, prerelease: a.prerelease });
|
|
567
682
|
}
|
|
568
683
|
}
|
|
569
684
|
// ── Commander (direct commands) ────────────────────────────────────────
|
|
@@ -583,55 +698,73 @@ function setupCommander() {
|
|
|
583
698
|
// Issues
|
|
584
699
|
const i = program.command('issues').description('📋 Manage issues');
|
|
585
700
|
i.command('list').option('-s, --state <s>', '', 'open').option('-l, --labels <l>').option('-n, --limit <n>', '', '50')
|
|
586
|
-
.action((o) => issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }));
|
|
701
|
+
.action(async (o) => { await issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }); });
|
|
587
702
|
i.command('create').requiredOption('-t, --title <t>').option('-b, --body <b>').option('-l, --labels <l>')
|
|
588
|
-
.action((o) => issues.createIssue(o));
|
|
703
|
+
.action(async (o) => { await issues.createIssue(o); });
|
|
589
704
|
i.command('bulk').requiredOption('-f, --file <f>').option('-d, --dry-run').option('--start <n>', '', '1').option('--end <n>', '', '999')
|
|
590
|
-
.action((o) => issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }));
|
|
591
|
-
i.command('close <n>').action((n) => issues.closeIssue(parseInt(n)));
|
|
592
|
-
i.command('reopen <n>').action((n) => issues.reopenIssue(parseInt(n)));
|
|
593
|
-
i.command('delete <n>').action((n) => issues.deleteIssue(parseInt(n)));
|
|
594
|
-
i.command('assign <n> <users...>').action((n, u) => issues.assignIssue(parseInt(n), u));
|
|
595
|
-
i.command('assign-best <n>').action((n) => issues.assignBest(parseInt(n)));
|
|
596
|
-
i.command('search <q>').action((q) => issues.searchIssues(q));
|
|
597
|
-
i.command('label <n> <labels...>').action((n, l) => issues.labelIssue(parseInt(n), l));
|
|
705
|
+
.action(async (o) => { await issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }); });
|
|
706
|
+
i.command('close <n>').action(async (n) => { await issues.closeIssue(parseInt(n)); });
|
|
707
|
+
i.command('reopen <n>').action(async (n) => { await issues.reopenIssue(parseInt(n)); });
|
|
708
|
+
i.command('delete <n>').action(async (n) => { await issues.deleteIssue(parseInt(n)); });
|
|
709
|
+
i.command('assign <n> <users...>').action(async (n, u) => { await issues.assignIssue(parseInt(n), u); });
|
|
710
|
+
i.command('assign-best <n>').action(async (n) => { await issues.assignBest(parseInt(n)); });
|
|
711
|
+
i.command('search <q>').action(async (q) => { await issues.searchIssues(q); });
|
|
712
|
+
i.command('label <n> <labels...>').action(async (n, l) => { await issues.labelIssue(parseInt(n), l); });
|
|
598
713
|
// PRs
|
|
599
714
|
const p = program.command('prs').description('🔀 Manage pull requests');
|
|
600
715
|
p.command('list').option('-s, --state <s>', '', 'open').option('-n, --limit <n>', '', '50')
|
|
601
|
-
.action((o) => prs.listPRs({ state: o.state, limit: parseInt(o.limit) }));
|
|
716
|
+
.action(async (o) => { await prs.listPRs({ state: o.state, limit: parseInt(o.limit) }); });
|
|
602
717
|
p.command('merge <n>').option('-m, --method <m>', '', 'squash').option('--message <msg>').option('-f, --force', 'Skip CI checks')
|
|
603
|
-
.action((n, o) => prs.mergePR(parseInt(n), o));
|
|
604
|
-
p.command('close <n>').action((n) => prs.closePR(parseInt(n)));
|
|
605
|
-
p.command('review <n>').action((n) => prs.reviewPR(parseInt(n)));
|
|
606
|
-
p.command('approve <n>').action((n) => prs.approvePR(parseInt(n)));
|
|
607
|
-
p.command('diff <n>').action((n) => prs.diffPR(parseInt(n)));
|
|
718
|
+
.action(async (n, o) => { await prs.mergePR(parseInt(n), o); });
|
|
719
|
+
p.command('close <n>').action(async (n) => { await prs.closePR(parseInt(n)); });
|
|
720
|
+
p.command('review <n>').action(async (n) => { await prs.reviewPR(parseInt(n)); });
|
|
721
|
+
p.command('approve <n>').action(async (n) => { await prs.approvePR(parseInt(n)); });
|
|
722
|
+
p.command('diff <n>').action(async (n) => { await prs.diffPR(parseInt(n)); });
|
|
608
723
|
// Repos
|
|
609
724
|
const r = program.command('repo').description('📦 Manage repositories');
|
|
610
725
|
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '50')
|
|
611
|
-
.action((o) => repos.listRepos({ org: o.org, limit: parseInt(o.limit) }));
|
|
726
|
+
.action(async (o) => { await repos.listRepos({ org: o.org, limit: parseInt(o.limit) }); });
|
|
612
727
|
r.command('create <name>').option('-o, --org <o>').option('-p, --private').option('-d, --description <d>')
|
|
613
|
-
.action((n, o) => repos.createRepo(n, o));
|
|
614
|
-
r.command('delete <name>').option('-o, --org <o>').action((n, o) => repos.deleteRepo(n, o));
|
|
615
|
-
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action((n, o) => repos.cloneRepo(n, o));
|
|
616
|
-
r.command('info <name>').option('-o, --org <o>').action((n, o) => repos.repoInfo(n, o));
|
|
617
|
-
r.command('topics <name> <topics...>').option('-o, --org <o>').action((n, t, o) => repos.setTopics(n, t, o));
|
|
728
|
+
.action(async (n, o) => { await repos.createRepo(n, o); });
|
|
729
|
+
r.command('delete <name>').option('-o, --org <o>').action(async (n, o) => { await repos.deleteRepo(n, o); });
|
|
730
|
+
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action(async (n, o) => { await repos.cloneRepo(n, o); });
|
|
731
|
+
r.command('info <name>').option('-o, --org <o>').action(async (n, o) => { await repos.repoInfo(n, o); });
|
|
732
|
+
r.command('topics <name> <topics...>').option('-o, --org <o>').action(async (n, t, o) => { await repos.setTopics(n, t, o); });
|
|
618
733
|
// Contributors
|
|
619
734
|
const c = program.command('contributors').description('🏆 Manage contributors');
|
|
620
|
-
c.command('score <user>').action((u) => contributors.scoreUser(u));
|
|
621
|
-
c.command('rank <issue>').action((n) => contributors.rankApplicants(parseInt(n)));
|
|
622
|
-
c.command('list').option('-n, --limit <n>', '', '50').action((o) => contributors.listContributors({ limit: parseInt(o.limit) }));
|
|
735
|
+
c.command('score <user>').action(async (u) => { await contributors.scoreUser(u); });
|
|
736
|
+
c.command('rank <issue>').action(async (n) => { await contributors.rankApplicants(parseInt(n)); });
|
|
737
|
+
c.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await contributors.listContributors({ limit: parseInt(o.limit) }); });
|
|
623
738
|
// Releases
|
|
624
739
|
const rel = program.command('release').description('🚀 Manage releases');
|
|
625
740
|
rel.command('create <tag>').option('-n, --name <n>').option('-b, --body <b>').option('-d, --draft').option('-p, --prerelease').option('--no-generate')
|
|
626
|
-
.action((t, o) => releases.createRelease(t, o));
|
|
627
|
-
rel.command('list').option('-n, --limit <n>', '', '50').action((o) => releases.listReleases({ limit: parseInt(o.limit) }));
|
|
741
|
+
.action(async (t, o) => { await releases.createRelease(t, o); });
|
|
742
|
+
rel.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await releases.listReleases({ limit: parseInt(o.limit) }); });
|
|
743
|
+
// Contributor Commands
|
|
744
|
+
// ILLUSTRATION: How to start?
|
|
745
|
+
// Option A: `gitpadi start https://github.com/owner/repo`
|
|
746
|
+
// Option B: Interactive Menu -> Contributor Mode -> Start Contribution
|
|
747
|
+
program.command('start <url>').description('🚀 Start contribution (fork, clone & branch)').action(async (u) => { await contribute.forkAndClone(u); });
|
|
748
|
+
program.command('sync').description('🔄 Sync with upstream').action(async () => { await contribute.syncBranch(); });
|
|
749
|
+
program.command('submit').description('🚀 Submit PR (add, commit, push & PR)')
|
|
750
|
+
.option('-t, --title <t>', 'PR title')
|
|
751
|
+
.option('-m, --message <m>', 'Commit message')
|
|
752
|
+
.option('-i, --issue <n>', 'Issue number')
|
|
753
|
+
.action(async (o) => {
|
|
754
|
+
await contribute.submitPR({
|
|
755
|
+
title: o.title || 'Automated PR',
|
|
756
|
+
message: o.message || o.title,
|
|
757
|
+
issue: o.issue ? parseInt(o.issue) : undefined
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
program.command('logs').description('📋 View Action logs').action(async () => { await contribute.viewLogs(); });
|
|
628
761
|
return program;
|
|
629
762
|
}
|
|
630
763
|
// ── Entry Point ────────────────────────────────────────────────────────
|
|
631
764
|
async function main() {
|
|
632
765
|
if (process.argv.length <= 2) {
|
|
633
766
|
await bootSequence();
|
|
634
|
-
await
|
|
767
|
+
await ensureAuthenticated();
|
|
635
768
|
await mainMenu();
|
|
636
769
|
}
|
|
637
770
|
else {
|