gitpadi 2.0.0 → 2.0.2
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 +4 -0
- package/dist/applicant-scorer.js +256 -0
- package/dist/cli.js +789 -0
- package/dist/commands/contribute.js +291 -0
- package/dist/commands/contributors.js +101 -0
- package/dist/commands/issues.js +319 -0
- package/dist/commands/prs.js +229 -0
- package/dist/commands/releases.js +53 -0
- package/dist/commands/repos.js +128 -0
- package/dist/core/github.js +106 -0
- package/dist/core/scorer.js +95 -0
- package/dist/create-issues.js +179 -0
- package/dist/pr-review.js +117 -0
- package/package.json +6 -3
- package/src/applicant-scorer.ts +1 -1
- package/src/cli.ts +345 -170
- package/src/commands/contribute.ts +331 -0
- package/src/commands/contributors.ts +1 -1
- package/src/commands/issues.ts +76 -9
- package/src/commands/prs.ts +1 -1
- package/src/commands/releases.ts +1 -1
- package/src/commands/repos.ts +41 -26
- package/src/core/github.ts +99 -15
- package/src/create-issues.ts +1 -1
- package/src/pr-review.ts +1 -1
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
// ═══════════════════════════════════════════════════════════════════════
|
|
3
3
|
// GitPadi v2.0 — Your AI-Powered GitHub Management Terminal
|
|
4
4
|
// "Built different. Run different."
|
|
@@ -9,15 +9,20 @@ import chalk from 'chalk';
|
|
|
9
9
|
import inquirer from 'inquirer';
|
|
10
10
|
import gradient from 'gradient-string';
|
|
11
11
|
import figlet from 'figlet';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
|
|
12
15
|
import boxen from 'boxen';
|
|
13
16
|
import { createSpinner } from 'nanospinner';
|
|
14
|
-
import {
|
|
17
|
+
import { Octokit } from '@octokit/rest';
|
|
18
|
+
import { initGitHub, setRepo, getOwner, getRepo, getOctokit, loadConfig, saveConfig, getToken } from './core/github.js';
|
|
15
19
|
|
|
16
20
|
import * as issues from './commands/issues.js';
|
|
17
21
|
import * as prs from './commands/prs.js';
|
|
18
22
|
import * as repos from './commands/repos.js';
|
|
19
23
|
import * as contributors from './commands/contributors.js';
|
|
20
24
|
import * as releases from './commands/releases.js';
|
|
25
|
+
import * as contribute from './commands/contribute.js';
|
|
21
26
|
|
|
22
27
|
const VERSION = '2.0.0';
|
|
23
28
|
|
|
@@ -123,116 +128,207 @@ async function bootSequence() {
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
// ── Onboarding ─────────────────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Ensures we have a valid GitHub token
|
|
133
|
+
*/
|
|
134
|
+
async function ensureAuthenticated() {
|
|
135
|
+
initGitHub();
|
|
136
|
+
let token = getToken();
|
|
137
|
+
|
|
138
|
+
if (token) {
|
|
139
|
+
try {
|
|
140
|
+
const octokit = getOctokit();
|
|
141
|
+
await octokit.users.getAuthenticated();
|
|
142
|
+
// Silent success if token is valid
|
|
143
|
+
return;
|
|
144
|
+
} catch {
|
|
145
|
+
console.log(red(' ❌ Saved session invalid. Re-authenticating...'));
|
|
146
|
+
token = '';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(neon(' ⚡ Authentication — let\'s connect you to GitHub.\n'));
|
|
151
|
+
|
|
152
|
+
const { t } = await inquirer.prompt([{
|
|
153
|
+
type: 'password',
|
|
154
|
+
name: 't',
|
|
155
|
+
message: magenta('🔑 GitHub Token') + dim(' (ghp_xxx):'),
|
|
156
|
+
mask: '•',
|
|
157
|
+
validate: async (v: string) => {
|
|
158
|
+
if (!v.startsWith('ghp_') && !v.startsWith('github_pat_')) {
|
|
159
|
+
return 'Token should start with ghp_ or github_pat_';
|
|
160
|
+
}
|
|
161
|
+
const spinner = createSpinner(dim('Validating token...')).start();
|
|
162
|
+
try {
|
|
163
|
+
const tempOctokit = new Octokit({ auth: v });
|
|
164
|
+
await tempOctokit.users.getAuthenticated();
|
|
165
|
+
spinner.success({ text: green('Token valid!') });
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
spinner.error({ text: red('Invalid token - GitHub rejected it.') });
|
|
169
|
+
return 'Invalid token';
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
}]);
|
|
173
|
+
|
|
174
|
+
initGitHub(t);
|
|
175
|
+
saveConfig({ token: t, owner: getOwner() || '', repo: getRepo() || '' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Ensures we have a target repository (Owner/Repo)
|
|
180
|
+
*/
|
|
181
|
+
async function ensureTargetRepo() {
|
|
182
|
+
let owner = getOwner();
|
|
183
|
+
let repo = getRepo();
|
|
184
|
+
|
|
185
|
+
if (owner && repo) {
|
|
137
186
|
return;
|
|
138
187
|
}
|
|
139
188
|
|
|
140
|
-
console.log(neon('
|
|
189
|
+
console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
|
|
141
190
|
|
|
142
191
|
const answers = await inquirer.prompt([
|
|
143
|
-
{
|
|
144
|
-
type: 'password',
|
|
145
|
-
name: 'token',
|
|
146
|
-
message: magenta('🔑 GitHub Token') + dim(' (ghp_xxx):'),
|
|
147
|
-
mask: '•',
|
|
148
|
-
validate: (v: string) => v.startsWith('ghp_') || v.startsWith('github_pat_') ? true : 'Token should start with ghp_ or github_pat_',
|
|
149
|
-
when: !savedToken,
|
|
150
|
-
},
|
|
151
192
|
{
|
|
152
193
|
type: 'input',
|
|
153
194
|
name: 'owner',
|
|
154
195
|
message: cyan('👤 GitHub Owner/Org:'),
|
|
155
|
-
default:
|
|
196
|
+
default: owner || '',
|
|
156
197
|
validate: (v: string) => v.length > 0 || 'Required',
|
|
157
|
-
when: !savedOwner,
|
|
158
198
|
},
|
|
159
199
|
{
|
|
160
200
|
type: 'input',
|
|
161
201
|
name: 'repo',
|
|
162
202
|
message: cyan('📦 Repository name:'),
|
|
163
|
-
default:
|
|
203
|
+
default: repo || '',
|
|
164
204
|
validate: (v: string) => v.length > 0 || 'Required',
|
|
165
|
-
when: !savedRepo,
|
|
166
205
|
},
|
|
167
206
|
]);
|
|
168
207
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const repo = answers.repo || savedRepo;
|
|
172
|
-
|
|
173
|
-
initGitHub(token, owner, repo);
|
|
174
|
-
|
|
175
|
-
const spinner = createSpinner(dim('Connecting to GitHub...')).start();
|
|
176
|
-
await sleep(800);
|
|
177
|
-
spinner.success({ text: green(`Locked in → ${cyan(`${owner}/${repo}`)}`) });
|
|
208
|
+
setRepo(answers.owner, answers.repo);
|
|
209
|
+
saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
|
|
178
210
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
yellow(' export GITHUB_TOKEN=ghp_xxx\n') +
|
|
183
|
-
yellow(' export GITHUB_OWNER=' + owner + '\n') +
|
|
184
|
-
yellow(' export GITHUB_REPO=' + repo),
|
|
185
|
-
{ padding: 1, borderColor: 'yellow', dimBorder: true, borderStyle: 'round' }
|
|
186
|
-
));
|
|
211
|
+
const spinner = createSpinner(dim('Connecting...')).start();
|
|
212
|
+
await sleep(400);
|
|
213
|
+
spinner.success({ text: green(`Locked in → ${cyan(`${answers.owner}/${answers.repo}`)}`) });
|
|
187
214
|
console.log('');
|
|
188
215
|
}
|
|
189
216
|
|
|
190
|
-
// ──
|
|
217
|
+
// ── Mode Selector ──────────────────────────────────────────────────────
|
|
191
218
|
async function mainMenu() {
|
|
192
219
|
while (true) {
|
|
193
220
|
line('═');
|
|
194
|
-
console.log(cyber(' ⟨ GITPADI
|
|
195
|
-
console.log(dim(' Select
|
|
221
|
+
console.log(cyber(' ⟨ GITPADI MODE SELECTOR ⟩'));
|
|
222
|
+
console.log(dim(' Select your workflow persona to continue'));
|
|
196
223
|
line('═');
|
|
197
224
|
console.log('');
|
|
198
225
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
const { mode } = await inquirer.prompt([{
|
|
227
|
+
type: 'list',
|
|
228
|
+
name: 'mode',
|
|
229
|
+
message: bold('Choose your path:'),
|
|
230
|
+
choices: [
|
|
231
|
+
{ name: `${cyan('✨')} ${bold('Contributor Mode')} ${dim('— fork, clone, sync, submit PRs')}`, value: 'contributor' },
|
|
232
|
+
{ name: `${magenta('🛠️')} ${bold('Maintainer Mode')} ${dim('— manage issues, PRs, contributors')}`, value: 'maintainer' },
|
|
233
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
234
|
+
{ name: `${dim('👋')} ${dim('Exit')}`, value: 'exit' },
|
|
235
|
+
],
|
|
236
|
+
loop: false,
|
|
237
|
+
}]);
|
|
238
|
+
|
|
239
|
+
if (mode === 'contributor') await safeMenu(contributorMenu);
|
|
240
|
+
else if (mode === 'maintainer') {
|
|
241
|
+
await ensureTargetRepo();
|
|
242
|
+
await safeMenu(maintainerMenu);
|
|
243
|
+
}
|
|
244
|
+
else break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Contributor Menu ───────────────────────────────────────────────────
|
|
249
|
+
async function contributorMenu() {
|
|
250
|
+
while (true) {
|
|
251
|
+
line('═');
|
|
252
|
+
console.log(cyan(' ✨ GITPADI CONTRIBUTOR WORKSPACE'));
|
|
253
|
+
console.log(dim(' Automating forking, syncing, and PR delivery'));
|
|
254
|
+
line('═');
|
|
255
|
+
|
|
256
|
+
// Auto-check for updates if in a repo
|
|
257
|
+
if (getOwner() && getRepo()) {
|
|
258
|
+
await contribute.syncBranch();
|
|
226
259
|
}
|
|
227
260
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
const { action } = await inquirer.prompt([{
|
|
262
|
+
type: 'list',
|
|
263
|
+
name: 'action',
|
|
264
|
+
message: bold('Contributor Action:'),
|
|
265
|
+
choices: [
|
|
266
|
+
{ name: `${cyan('🚀')} ${bold('Start Contribution')} ${dim('— fork, clone & branch')}`, value: 'start' },
|
|
267
|
+
{ name: `${green('🔄')} ${bold('Sync with Upstream')} ${dim('— pull latest changes')}`, value: 'sync' },
|
|
268
|
+
{ name: `${yellow('📋')} ${bold('View Action Logs')} ${dim('— check PR/commit status')}`, value: 'logs' },
|
|
269
|
+
{ name: `${magenta('🚀')} ${bold('Submit PR')} ${dim('— add, commit, push & PR')}`, value: 'submit' },
|
|
270
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
271
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
272
|
+
],
|
|
273
|
+
loop: false,
|
|
274
|
+
}]);
|
|
275
|
+
|
|
276
|
+
if (action === 'back') break;
|
|
277
|
+
|
|
278
|
+
if (action === 'start') {
|
|
279
|
+
const { url } = await ask([{ type: 'input', name: 'url', message: 'Enter Repo or Issue URL:' }]);
|
|
280
|
+
await contribute.forkAndClone(url);
|
|
281
|
+
} else {
|
|
282
|
+
// These actions require a target repo
|
|
283
|
+
await ensureTargetRepo();
|
|
284
|
+
|
|
285
|
+
if (action === 'sync') {
|
|
286
|
+
await contribute.syncBranch();
|
|
287
|
+
} else if (action === 'logs') {
|
|
288
|
+
await contribute.viewLogs();
|
|
289
|
+
} else if (action === 'submit') {
|
|
290
|
+
const { title, message, issue } = await ask([
|
|
291
|
+
{ type: 'input', name: 'title', message: 'PR Title:' },
|
|
292
|
+
{ type: 'input', name: 'message', message: 'Commit message (optional):' },
|
|
293
|
+
{ type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
|
|
294
|
+
]);
|
|
295
|
+
await contribute.submitPR({
|
|
296
|
+
title,
|
|
297
|
+
message: message || title,
|
|
298
|
+
issue: issue ? parseInt(issue) : undefined
|
|
299
|
+
});
|
|
300
|
+
}
|
|
235
301
|
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Maintainer Menu ────────────────────────────────────────────────────
|
|
306
|
+
async function maintainerMenu() {
|
|
307
|
+
while (true) {
|
|
308
|
+
line('═');
|
|
309
|
+
console.log(magenta(' 🛠️ GITPADI MAINTAINER PANEL'));
|
|
310
|
+
console.log(dim(' Managing repository health and contributor intake'));
|
|
311
|
+
line('═');
|
|
312
|
+
console.log('');
|
|
313
|
+
|
|
314
|
+
const { category } = await inquirer.prompt([{
|
|
315
|
+
type: 'list',
|
|
316
|
+
name: 'category',
|
|
317
|
+
message: bold('Select operation:'),
|
|
318
|
+
choices: [
|
|
319
|
+
{ name: `${magenta('📋')} ${bold('Issues')} ${dim('— create, close, delete, assign, search')}`, value: 'issues' },
|
|
320
|
+
{ name: `${cyan('🔀')} ${bold('Pull Requests')} ${dim('— merge, review, approve, diff')}`, value: 'prs' },
|
|
321
|
+
{ name: `${green('📦')} ${bold('Repositories')} ${dim('— create, delete, clone, info')}`, value: 'repos' },
|
|
322
|
+
{ name: `${yellow('🏆')} ${bold('Contributors')} ${dim('— score, rank, auto-assign best')}`, value: 'contributors' },
|
|
323
|
+
{ name: `${red('🚀')} ${bold('Releases')} ${dim('— create, list, manage')}`, value: 'releases' },
|
|
324
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
325
|
+
{ name: `${dim('⚙️')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
326
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
327
|
+
],
|
|
328
|
+
loop: false,
|
|
329
|
+
}]);
|
|
330
|
+
|
|
331
|
+
if (category === 'back') break;
|
|
236
332
|
|
|
237
333
|
if (category === 'switch') {
|
|
238
334
|
await safeMenu(async () => {
|
|
@@ -249,10 +345,10 @@ async function mainMenu() {
|
|
|
249
345
|
}
|
|
250
346
|
|
|
251
347
|
if (category === 'issues') await safeMenu(issueMenu);
|
|
252
|
-
if (category === 'prs') await safeMenu(prMenu);
|
|
253
|
-
if (category === 'repos') await safeMenu(repoMenu);
|
|
254
|
-
if (category === 'contributors') await safeMenu(
|
|
255
|
-
if (category === 'releases') await safeMenu(releaseMenu);
|
|
348
|
+
else if (category === 'prs') await safeMenu(prMenu);
|
|
349
|
+
else if (category === 'repos') await safeMenu(repoMenu);
|
|
350
|
+
else if (category === 'contributors') await safeMenu(contributorScoringMenu);
|
|
351
|
+
else if (category === 'releases') await safeMenu(releaseMenu);
|
|
256
352
|
|
|
257
353
|
console.log('');
|
|
258
354
|
}
|
|
@@ -263,11 +359,11 @@ async function issueMenu() {
|
|
|
263
359
|
const { action } = await inquirer.prompt([{
|
|
264
360
|
type: 'list', name: 'action', message: magenta('📋 Issue Operation:'),
|
|
265
361
|
choices: [
|
|
266
|
-
{ name: ` ${dim('⬅ Back
|
|
362
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
267
363
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
268
364
|
{ name: ` ${green('▸')} List open issues`, value: 'list' },
|
|
269
365
|
{ name: ` ${green('▸')} Create single issue`, value: 'create' },
|
|
270
|
-
{ name: ` ${
|
|
366
|
+
{ name: ` ${magenta('▸')} Bulk create from file (JSON/MD)`, value: 'bulk' },
|
|
271
367
|
{ name: ` ${red('▸')} Close issue`, value: 'close' },
|
|
272
368
|
{ name: ` ${green('▸')} Reopen issue`, value: 'reopen' },
|
|
273
369
|
{ name: ` ${red('▸')} Delete (close & lock)`, value: 'delete' },
|
|
@@ -284,30 +380,25 @@ async function issueMenu() {
|
|
|
284
380
|
const opts = await ask([
|
|
285
381
|
{ type: 'list', name: 'state', message: 'State:', choices: ['open', 'closed', 'all'], default: 'open' },
|
|
286
382
|
{ type: 'input', name: 'labels', message: dim('Filter by labels (optional, q=back):'), default: '' },
|
|
383
|
+
{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' },
|
|
287
384
|
]);
|
|
288
|
-
await issues.listIssues({ state: opts.state, labels: opts.labels || undefined });
|
|
385
|
+
await issues.listIssues({ state: opts.state, labels: opts.labels || undefined, limit: parseInt(opts.limit) });
|
|
289
386
|
} else if (action === 'create') {
|
|
290
387
|
const a = await ask([
|
|
291
|
-
{ type: 'input', name: 'title', message: yellow('Issue title')
|
|
292
|
-
{ type: '
|
|
388
|
+
{ type: 'input', name: 'title', message: yellow('Issue title'), validate: (v: string) => v.length > 0 || 'Required' },
|
|
389
|
+
{ type: 'input', name: 'body', message: dim('Issue body (optional):'), default: '' },
|
|
293
390
|
{ type: 'input', name: 'labels', message: dim('Labels (comma-separated):'), default: '' },
|
|
294
391
|
]);
|
|
295
392
|
await issues.createIssue(a);
|
|
296
393
|
} else if (action === 'bulk') {
|
|
297
394
|
const a = await ask([
|
|
298
|
-
{ type: 'input', name: 'file', message: yellow('📁 Path to issues JSON') + dim(' (q=back):') },
|
|
395
|
+
{ type: 'input', name: 'file', message: yellow('📁 Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
299
396
|
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
300
397
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
301
398
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
302
399
|
]);
|
|
303
400
|
await issues.createIssuesFromFile(a.file, { dryRun: a.dryRun, start: a.start, end: a.end });
|
|
304
401
|
} else if (action === 'assign-best') {
|
|
305
|
-
// ── Smart Auto-Assign Flow ──
|
|
306
|
-
// 1. Fetch all open issues with comments
|
|
307
|
-
// 2. Filter to ones with applicant comments
|
|
308
|
-
// 3. Let user pick from a list
|
|
309
|
-
// 4. Show applicants + scores, then assign best
|
|
310
|
-
|
|
311
402
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
312
403
|
const octokit = getOctokit();
|
|
313
404
|
|
|
@@ -315,10 +406,7 @@ async function issueMenu() {
|
|
|
315
406
|
owner: getOwner(), repo: getRepo(), state: 'open', per_page: 50,
|
|
316
407
|
});
|
|
317
408
|
|
|
318
|
-
|
|
319
|
-
const realIssues = allIssues.filter((i) => !i.pull_request && i.comments > 0);
|
|
320
|
-
|
|
321
|
-
// Check each issue for applicant comments
|
|
409
|
+
const realIssues = allIssues.filter((i: any) => !i.pull_request && i.comments > 0);
|
|
322
410
|
const issuesWithApplicants: Array<{ number: number; title: string; applicants: string[]; labels: string[] }> = [];
|
|
323
411
|
|
|
324
412
|
for (const issue of realIssues) {
|
|
@@ -327,7 +415,7 @@ async function issueMenu() {
|
|
|
327
415
|
});
|
|
328
416
|
|
|
329
417
|
const applicantUsers = new Set<string>();
|
|
330
|
-
comments.forEach((c) => {
|
|
418
|
+
comments.forEach((c: any) => {
|
|
331
419
|
if (c.user?.login && c.user.login !== 'github-actions[bot]') {
|
|
332
420
|
applicantUsers.add(c.user.login);
|
|
333
421
|
}
|
|
@@ -338,7 +426,7 @@ async function issueMenu() {
|
|
|
338
426
|
number: issue.number,
|
|
339
427
|
title: issue.title.substring(0, 45),
|
|
340
428
|
applicants: Array.from(applicantUsers),
|
|
341
|
-
labels: issue.labels.map((l) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
429
|
+
labels: issue.labels.map((l: any) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
342
430
|
});
|
|
343
431
|
}
|
|
344
432
|
}
|
|
@@ -352,7 +440,6 @@ async function issueMenu() {
|
|
|
352
440
|
|
|
353
441
|
console.log(`\n${bold(`🏆 Issues with Applicants`)} (${issuesWithApplicants.length})\n`);
|
|
354
442
|
|
|
355
|
-
// Let user pick an issue
|
|
356
443
|
const { picked } = await inquirer.prompt([{
|
|
357
444
|
type: 'list', name: 'picked', message: yellow('Select an issue:'),
|
|
358
445
|
choices: [
|
|
@@ -365,8 +452,6 @@ async function issueMenu() {
|
|
|
365
452
|
}]);
|
|
366
453
|
|
|
367
454
|
if (picked === -1) return;
|
|
368
|
-
|
|
369
|
-
// Run the scoring
|
|
370
455
|
await issues.assignBest(picked);
|
|
371
456
|
|
|
372
457
|
} else if (action === 'close' || action === 'reopen' || action === 'delete') {
|
|
@@ -401,7 +486,7 @@ async function prMenu() {
|
|
|
401
486
|
const { action } = await inquirer.prompt([{
|
|
402
487
|
type: 'list', name: 'action', message: cyan('🔀 PR Operation:'),
|
|
403
488
|
choices: [
|
|
404
|
-
{ name: ` ${dim('⬅ Back
|
|
489
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
405
490
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
406
491
|
{ name: ` ${green('▸')} List pull requests`, value: 'list' },
|
|
407
492
|
{ name: ` ${green('▸')} Merge PR ${dim('(checks CI first)')}`, value: 'merge' },
|
|
@@ -416,8 +501,11 @@ async function prMenu() {
|
|
|
416
501
|
if (action === 'back') return;
|
|
417
502
|
|
|
418
503
|
if (action === 'list') {
|
|
419
|
-
const
|
|
420
|
-
|
|
504
|
+
const a = await ask([
|
|
505
|
+
{ type: 'list', name: 'state', message: 'State:', choices: ['open', 'closed', 'all'], default: 'open' },
|
|
506
|
+
{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' },
|
|
507
|
+
]);
|
|
508
|
+
await prs.listPRs({ state: a.state, limit: parseInt(a.limit) });
|
|
421
509
|
} else if (action === 'merge' || action === 'force-merge') {
|
|
422
510
|
const a = await ask([
|
|
423
511
|
{ type: 'input', name: 'n', message: yellow('PR #') + dim(' (q=back):') },
|
|
@@ -445,7 +533,7 @@ async function repoMenu() {
|
|
|
445
533
|
const { action } = await inquirer.prompt([{
|
|
446
534
|
type: 'list', name: 'action', message: green('📦 Repo Operation:'),
|
|
447
535
|
choices: [
|
|
448
|
-
{ name: ` ${dim('⬅ Back
|
|
536
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
449
537
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
450
538
|
{ name: ` ${green('▸')} List repositories`, value: 'list' },
|
|
451
539
|
{ name: ` ${green('▸')} Create repo`, value: 'create' },
|
|
@@ -459,8 +547,11 @@ async function repoMenu() {
|
|
|
459
547
|
if (action === 'back') return;
|
|
460
548
|
|
|
461
549
|
if (action === 'list') {
|
|
462
|
-
const
|
|
463
|
-
|
|
550
|
+
const a = await ask([
|
|
551
|
+
{ type: 'input', name: 'org', message: cyan('Org (blank for yours, q=back):'), default: '' },
|
|
552
|
+
{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' },
|
|
553
|
+
]);
|
|
554
|
+
await repos.listRepos({ org: a.org || undefined, limit: parseInt(a.limit) });
|
|
464
555
|
} else if (action === 'create') {
|
|
465
556
|
const a = await ask([
|
|
466
557
|
{ type: 'input', name: 'name', message: yellow('📦 Repo name') + dim(' (q=back):') },
|
|
@@ -470,26 +561,88 @@ async function repoMenu() {
|
|
|
470
561
|
]);
|
|
471
562
|
await repos.createRepo(a.name, { org: a.org || undefined, description: a.description, private: a.isPrivate });
|
|
472
563
|
} else if (action === 'delete') {
|
|
473
|
-
const
|
|
474
|
-
{ type: 'input', name: '
|
|
475
|
-
{ type: 'input', name: 'org', message: 'Org:', default: getOwner() },
|
|
564
|
+
const { org } = await ask([
|
|
565
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
476
566
|
]);
|
|
567
|
+
|
|
568
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
569
|
+
let repoToDelete: string;
|
|
570
|
+
|
|
571
|
+
if (fetchedRepos.length > 0) {
|
|
572
|
+
const { selection } = await inquirer.prompt([{
|
|
573
|
+
type: 'list',
|
|
574
|
+
name: 'selection',
|
|
575
|
+
message: red('📦 Select Repo to DELETE:'),
|
|
576
|
+
choices: [
|
|
577
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
578
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
579
|
+
]
|
|
580
|
+
}]);
|
|
581
|
+
if (selection === 'back') return;
|
|
582
|
+
repoToDelete = selection;
|
|
583
|
+
} else {
|
|
584
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: red('📦 Repo name to DELETE:') }]);
|
|
585
|
+
repoToDelete = name;
|
|
586
|
+
}
|
|
587
|
+
|
|
477
588
|
const { confirm } = await ask([
|
|
478
|
-
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${
|
|
589
|
+
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${repoToDelete}" to confirm deletion:`), validate: (v: string) => v === repoToDelete || 'Name doesn\'t match' },
|
|
479
590
|
]);
|
|
480
|
-
if (confirm ===
|
|
591
|
+
if (confirm === repoToDelete) await repos.deleteRepo(repoToDelete, { org });
|
|
481
592
|
} else if (action === 'clone') {
|
|
482
|
-
const
|
|
483
|
-
{ type: 'input', name: '
|
|
484
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
593
|
+
const { org } = await ask([
|
|
594
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
485
595
|
]);
|
|
486
|
-
|
|
596
|
+
|
|
597
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
598
|
+
let repoToClone: string;
|
|
599
|
+
|
|
600
|
+
if (fetchedRepos.length > 0) {
|
|
601
|
+
const { selection } = await inquirer.prompt([{
|
|
602
|
+
type: 'list',
|
|
603
|
+
name: 'selection',
|
|
604
|
+
message: cyan('📦 Select Repo to Clone:'),
|
|
605
|
+
choices: [
|
|
606
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
607
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
608
|
+
]
|
|
609
|
+
}]);
|
|
610
|
+
if (selection === 'back') return;
|
|
611
|
+
repoToClone = selection;
|
|
612
|
+
} else {
|
|
613
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name to Clone:') }]);
|
|
614
|
+
repoToClone = name;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const { dir } = await ask([
|
|
618
|
+
{ type: 'input', name: 'dir', message: dim('Destination path (leave blank for default):'), default: '' },
|
|
619
|
+
]);
|
|
620
|
+
await repos.cloneRepo(repoToClone, { org, dir: dir || undefined });
|
|
487
621
|
} else if (action === 'info') {
|
|
488
|
-
const
|
|
489
|
-
{ type: 'input', name: '
|
|
490
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
622
|
+
const { org } = await ask([
|
|
623
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
491
624
|
]);
|
|
492
|
-
|
|
625
|
+
|
|
626
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
627
|
+
let repoToInfo: string;
|
|
628
|
+
|
|
629
|
+
if (fetchedRepos.length > 0) {
|
|
630
|
+
const { selection } = await inquirer.prompt([{
|
|
631
|
+
type: 'list',
|
|
632
|
+
name: 'selection',
|
|
633
|
+
message: cyan('📦 Select Repo for Info:'),
|
|
634
|
+
choices: [
|
|
635
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
636
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
637
|
+
]
|
|
638
|
+
}]);
|
|
639
|
+
if (selection === 'back') return;
|
|
640
|
+
repoToInfo = selection;
|
|
641
|
+
} else {
|
|
642
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name:') }]);
|
|
643
|
+
repoToInfo = name;
|
|
644
|
+
}
|
|
645
|
+
await repos.repoInfo(repoToInfo, { org });
|
|
493
646
|
} else if (action === 'topics') {
|
|
494
647
|
const a = await ask([
|
|
495
648
|
{ type: 'input', name: 'name', message: cyan('Repo name:'), default: getRepo() },
|
|
@@ -499,12 +652,12 @@ async function repoMenu() {
|
|
|
499
652
|
}
|
|
500
653
|
}
|
|
501
654
|
|
|
502
|
-
// ── Contributor Menu
|
|
503
|
-
async function
|
|
655
|
+
// ── Contributor Scoring Menu (Maintainer Tool) ─────────────────────────
|
|
656
|
+
async function contributorScoringMenu() {
|
|
504
657
|
const { action } = await inquirer.prompt([{
|
|
505
658
|
type: 'list', name: 'action', message: yellow('🏆 Contributor Operation:'),
|
|
506
659
|
choices: [
|
|
507
|
-
{ name: ` ${dim('⬅ Back
|
|
660
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
508
661
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
509
662
|
{ name: ` ${yellow('▸')} Score a user`, value: 'score' },
|
|
510
663
|
{ name: ` ${yellow('▸')} Rank applicants for issue`, value: 'rank' },
|
|
@@ -520,8 +673,9 @@ async function contributorMenu() {
|
|
|
520
673
|
} else if (action === 'rank') {
|
|
521
674
|
const { n } = await ask([{ type: 'input', name: 'n', message: yellow('Issue #') + dim(' (q=back):') }]);
|
|
522
675
|
await contributors.rankApplicants(parseInt(n));
|
|
523
|
-
} else {
|
|
524
|
-
await
|
|
676
|
+
} else if (action === 'list') {
|
|
677
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
678
|
+
await contributors.listContributors({ limit: parseInt(a.limit) });
|
|
525
679
|
}
|
|
526
680
|
}
|
|
527
681
|
|
|
@@ -530,25 +684,27 @@ async function releaseMenu() {
|
|
|
530
684
|
const { action } = await inquirer.prompt([{
|
|
531
685
|
type: 'list', name: 'action', message: red('🚀 Release Operation:'),
|
|
532
686
|
choices: [
|
|
533
|
-
{ name: ` ${dim('⬅ Back
|
|
687
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
534
688
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
535
|
-
{ name: ` ${
|
|
536
|
-
{ name: ` ${
|
|
689
|
+
{ name: ` ${green('▸')} List releases`, value: 'list' },
|
|
690
|
+
{ name: ` ${green('▸')} Create release`, value: 'create' },
|
|
537
691
|
],
|
|
538
692
|
}]);
|
|
539
693
|
|
|
540
694
|
if (action === 'back') return;
|
|
541
695
|
|
|
542
|
-
if (action === '
|
|
696
|
+
if (action === 'list') {
|
|
697
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' }]);
|
|
698
|
+
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
699
|
+
} else if (action === 'create') {
|
|
543
700
|
const a = await ask([
|
|
544
|
-
{ type: 'input', name: 'tag', message: yellow('Tag (e.g
|
|
545
|
-
{ type: 'input', name: 'name', message: dim('Release name:'), default: '' },
|
|
701
|
+
{ type: 'input', name: 'tag', message: yellow('Tag (e.g. v1.0.0):') },
|
|
702
|
+
{ type: 'input', name: 'name', message: dim('Release name (optional):'), default: '' },
|
|
703
|
+
{ type: 'input', name: 'body', message: dim('Release body (optional):'), default: '' },
|
|
546
704
|
{ type: 'confirm', name: 'draft', message: 'Draft?', default: false },
|
|
547
|
-
{ type: 'confirm', name: 'prerelease', message: '
|
|
705
|
+
{ type: 'confirm', name: 'prerelease', message: 'Prerelease?', default: false },
|
|
548
706
|
]);
|
|
549
|
-
await releases.createRelease(a.tag, { name: a.name
|
|
550
|
-
} else {
|
|
551
|
-
await releases.listReleases({});
|
|
707
|
+
await releases.createRelease(a.tag, { name: a.name, body: a.body, draft: a.draft, prerelease: a.prerelease });
|
|
552
708
|
}
|
|
553
709
|
}
|
|
554
710
|
|
|
@@ -569,53 +725,72 @@ function setupCommander(): Command {
|
|
|
569
725
|
|
|
570
726
|
// Issues
|
|
571
727
|
const i = program.command('issues').description('📋 Manage issues');
|
|
572
|
-
i.command('list').option('-s, --state <s>', '', 'open').option('-l, --labels <l>').option('-n, --limit <n>', '', '
|
|
573
|
-
.action((o) => issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }));
|
|
728
|
+
i.command('list').option('-s, --state <s>', '', 'open').option('-l, --labels <l>').option('-n, --limit <n>', '', '50')
|
|
729
|
+
.action(async (o) => { await issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }); });
|
|
574
730
|
i.command('create').requiredOption('-t, --title <t>').option('-b, --body <b>').option('-l, --labels <l>')
|
|
575
|
-
.action((o) => issues.createIssue(o));
|
|
731
|
+
.action(async (o) => { await issues.createIssue(o); });
|
|
576
732
|
i.command('bulk').requiredOption('-f, --file <f>').option('-d, --dry-run').option('--start <n>', '', '1').option('--end <n>', '', '999')
|
|
577
|
-
.action((o) => issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }));
|
|
578
|
-
i.command('close <n>').action((n) => issues.closeIssue(parseInt(n)));
|
|
579
|
-
i.command('reopen <n>').action((n) => issues.reopenIssue(parseInt(n)));
|
|
580
|
-
i.command('delete <n>').action((n) => issues.deleteIssue(parseInt(n)));
|
|
581
|
-
i.command('assign <n> <users...>').action((n, u) => issues.assignIssue(parseInt(n), u));
|
|
582
|
-
i.command('assign-best <n>').action((n) => issues.assignBest(parseInt(n)));
|
|
583
|
-
i.command('search <q>').action((q) => issues.searchIssues(q));
|
|
584
|
-
i.command('label <n> <labels...>').action((n, l) => issues.labelIssue(parseInt(n), l));
|
|
733
|
+
.action(async (o) => { await issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }); });
|
|
734
|
+
i.command('close <n>').action(async (n) => { await issues.closeIssue(parseInt(n)); });
|
|
735
|
+
i.command('reopen <n>').action(async (n) => { await issues.reopenIssue(parseInt(n)); });
|
|
736
|
+
i.command('delete <n>').action(async (n) => { await issues.deleteIssue(parseInt(n)); });
|
|
737
|
+
i.command('assign <n> <users...>').action(async (n, u) => { await issues.assignIssue(parseInt(n), u); });
|
|
738
|
+
i.command('assign-best <n>').action(async (n) => { await issues.assignBest(parseInt(n)); });
|
|
739
|
+
i.command('search <q>').action(async (q) => { await issues.searchIssues(q); });
|
|
740
|
+
i.command('label <n> <labels...>').action(async (n, l) => { await issues.labelIssue(parseInt(n), l); });
|
|
585
741
|
|
|
586
742
|
// PRs
|
|
587
743
|
const p = program.command('prs').description('🔀 Manage pull requests');
|
|
588
|
-
p.command('list').option('-s, --state <s>', '', 'open').option('-n, --limit <n>', '', '
|
|
589
|
-
.action((o) => prs.listPRs({ state: o.state, limit: parseInt(o.limit) }));
|
|
744
|
+
p.command('list').option('-s, --state <s>', '', 'open').option('-n, --limit <n>', '', '50')
|
|
745
|
+
.action(async (o) => { await prs.listPRs({ state: o.state, limit: parseInt(o.limit) }); });
|
|
590
746
|
p.command('merge <n>').option('-m, --method <m>', '', 'squash').option('--message <msg>').option('-f, --force', 'Skip CI checks')
|
|
591
|
-
.action((n, o) => prs.mergePR(parseInt(n), o));
|
|
592
|
-
p.command('close <n>').action((n) => prs.closePR(parseInt(n)));
|
|
593
|
-
p.command('review <n>').action((n) => prs.reviewPR(parseInt(n)));
|
|
594
|
-
p.command('approve <n>').action((n) => prs.approvePR(parseInt(n)));
|
|
595
|
-
p.command('diff <n>').action((n) => prs.diffPR(parseInt(n)));
|
|
747
|
+
.action(async (n, o) => { await prs.mergePR(parseInt(n), o); });
|
|
748
|
+
p.command('close <n>').action(async (n) => { await prs.closePR(parseInt(n)); });
|
|
749
|
+
p.command('review <n>').action(async (n) => { await prs.reviewPR(parseInt(n)); });
|
|
750
|
+
p.command('approve <n>').action(async (n) => { await prs.approvePR(parseInt(n)); });
|
|
751
|
+
p.command('diff <n>').action(async (n) => { await prs.diffPR(parseInt(n)); });
|
|
596
752
|
|
|
597
753
|
// Repos
|
|
598
754
|
const r = program.command('repo').description('📦 Manage repositories');
|
|
599
|
-
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '
|
|
600
|
-
.action((o) => repos.listRepos({ org: o.org, limit: parseInt(o.limit) }));
|
|
755
|
+
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '50')
|
|
756
|
+
.action(async (o) => { await repos.listRepos({ org: o.org, limit: parseInt(o.limit) }); });
|
|
601
757
|
r.command('create <name>').option('-o, --org <o>').option('-p, --private').option('-d, --description <d>')
|
|
602
|
-
.action((n, o) => repos.createRepo(n, o));
|
|
603
|
-
r.command('delete <name>').option('-o, --org <o>').action((n, o) => repos.deleteRepo(n, o));
|
|
604
|
-
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action((n, o) => repos.cloneRepo(n, o));
|
|
605
|
-
r.command('info <name>').option('-o, --org <o>').action((n, o) => repos.repoInfo(n, o));
|
|
606
|
-
r.command('topics <name> <topics...>').option('-o, --org <o>').action((n, t, o) => repos.setTopics(n, t, o));
|
|
758
|
+
.action(async (n, o) => { await repos.createRepo(n, o); });
|
|
759
|
+
r.command('delete <name>').option('-o, --org <o>').action(async (n, o) => { await repos.deleteRepo(n, o); });
|
|
760
|
+
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action(async (n, o) => { await repos.cloneRepo(n, o); });
|
|
761
|
+
r.command('info <name>').option('-o, --org <o>').action(async (n, o) => { await repos.repoInfo(n, o); });
|
|
762
|
+
r.command('topics <name> <topics...>').option('-o, --org <o>').action(async (n, t, o) => { await repos.setTopics(n, t, o); });
|
|
607
763
|
|
|
608
764
|
// Contributors
|
|
609
765
|
const c = program.command('contributors').description('🏆 Manage contributors');
|
|
610
|
-
c.command('score <user>').action((u) => contributors.scoreUser(u));
|
|
611
|
-
c.command('rank <issue>').action((n) => contributors.rankApplicants(parseInt(n)));
|
|
612
|
-
c.command('list').option('-n, --limit <n>', '', '
|
|
766
|
+
c.command('score <user>').action(async (u) => { await contributors.scoreUser(u); });
|
|
767
|
+
c.command('rank <issue>').action(async (n) => { await contributors.rankApplicants(parseInt(n)); });
|
|
768
|
+
c.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await contributors.listContributors({ limit: parseInt(o.limit) }); });
|
|
613
769
|
|
|
614
770
|
// Releases
|
|
615
771
|
const rel = program.command('release').description('🚀 Manage releases');
|
|
616
772
|
rel.command('create <tag>').option('-n, --name <n>').option('-b, --body <b>').option('-d, --draft').option('-p, --prerelease').option('--no-generate')
|
|
617
|
-
.action((t, o) => releases.createRelease(t, o));
|
|
618
|
-
rel.command('list').option('-n, --limit <n>', '', '
|
|
773
|
+
.action(async (t, o) => { await releases.createRelease(t, o); });
|
|
774
|
+
rel.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await releases.listReleases({ limit: parseInt(o.limit) }); });
|
|
775
|
+
|
|
776
|
+
// Contributor Commands
|
|
777
|
+
// ILLUSTRATION: How to start?
|
|
778
|
+
// Option A: `gitpadi start https://github.com/owner/repo`
|
|
779
|
+
// Option B: Interactive Menu -> Contributor Mode -> Start Contribution
|
|
780
|
+
program.command('start <url>').description('🚀 Start contribution (fork, clone & branch)').action(async (u) => { await contribute.forkAndClone(u); });
|
|
781
|
+
program.command('sync').description('🔄 Sync with upstream').action(async () => { await contribute.syncBranch(); });
|
|
782
|
+
program.command('submit').description('🚀 Submit PR (add, commit, push & PR)')
|
|
783
|
+
.option('-t, --title <t>', 'PR title')
|
|
784
|
+
.option('-m, --message <m>', 'Commit message')
|
|
785
|
+
.option('-i, --issue <n>', 'Issue number')
|
|
786
|
+
.action(async (o) => {
|
|
787
|
+
await contribute.submitPR({
|
|
788
|
+
title: o.title || 'Automated PR',
|
|
789
|
+
message: o.message || o.title,
|
|
790
|
+
issue: o.issue ? parseInt(o.issue) : undefined
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
program.command('logs').description('📋 View Action logs').action(async () => { await contribute.viewLogs(); });
|
|
619
794
|
|
|
620
795
|
return program;
|
|
621
796
|
}
|
|
@@ -624,7 +799,7 @@ function setupCommander(): Command {
|
|
|
624
799
|
async function main() {
|
|
625
800
|
if (process.argv.length <= 2) {
|
|
626
801
|
await bootSequence();
|
|
627
|
-
await
|
|
802
|
+
await ensureAuthenticated();
|
|
628
803
|
await mainMenu();
|
|
629
804
|
} else {
|
|
630
805
|
const program = setupCommander();
|