gitpadi 2.0.2 ā 2.0.4
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 +47 -13
- package/dist/commands/contribute.js +53 -12
- package/dist/commands/issues.js +40 -21
- package/package.json +2 -2
- package/src/cli.ts +46 -13
- package/src/commands/contribute.ts +55 -12
- package/src/commands/issues.ts +47 -22
package/README.md
CHANGED
|
@@ -1,270 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
<h1 align="center">š¤ GitPadi</h1>
|
|
3
|
-
<p align="center"><strong>Your AI-powered GitHub management agent.</strong></p>
|
|
4
|
-
<p align="center">
|
|
5
|
-
Auto-score contributors, review PRs, manage issues & repos ā from your terminal or as a GitHub Action.
|
|
6
|
-
</p>
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="#-use-the-cli">CLI</a> ā¢
|
|
9
|
-
<a href="#-use-as-github-action">GitHub Action</a> ā¢
|
|
10
|
-
<a href="#-scoring-algorithm">Scoring Algorithm</a>
|
|
11
|
-
</p>
|
|
12
|
-
</p>
|
|
1
|
+
# š¤ GitPadi
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## ā” Two Ways to Use GitPadi
|
|
3
|
+
GitPadi is a GitHub management CLI and Action built to automate the repetitive parts of maintaining open-source projects.
|
|
17
4
|
|
|
18
|
-
|
|
19
|
-
|--------|----------|-------|
|
|
20
|
-
| **CLI** (`npx gitpadi`) | Hands-on management from your terminal | One command |
|
|
21
|
-
| **GitHub Action** | Automated scoring & reviews on every event | Copy one workflow file |
|
|
5
|
+
Whether you're a maintainer managing hundreds of issues or a contributor making your first PR, GitPadi turns multi-step GitHub workflows into single commands.
|
|
22
6
|
|
|
23
7
|
---
|
|
24
8
|
|
|
25
|
-
##
|
|
9
|
+
## š For Contributors
|
|
26
10
|
|
|
27
|
-
|
|
11
|
+
Stop worrying about forking, upstream remotes, and branch naming. GitPadi handles the plumbing so you can focus on the code.
|
|
28
12
|
|
|
13
|
+
### Start a new contribution
|
|
29
14
|
```bash
|
|
30
|
-
|
|
31
|
-
export GITHUB_OWNER=your-org
|
|
32
|
-
export GITHUB_REPO=your-repo
|
|
33
|
-
|
|
34
|
-
npx gitpadi
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
That launches the **interactive terminal**:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
41
|
-
ā š¤ GitPadi v2.0 ā
|
|
42
|
-
ā Your GitHub Management Companion ā
|
|
43
|
-
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
44
|
-
|
|
45
|
-
? What would you like to do?
|
|
46
|
-
š Manage Issues
|
|
47
|
-
š Manage Pull Requests
|
|
48
|
-
š¦ Manage Repositories
|
|
49
|
-
š Score Contributors
|
|
50
|
-
š Create Release
|
|
51
|
-
āļø Switch Repo
|
|
52
|
-
š Exit
|
|
15
|
+
npx gitpadi start <issue-url>
|
|
53
16
|
```
|
|
17
|
+
This single command:
|
|
18
|
+
1. **Forks** the repository to your account.
|
|
19
|
+
2. **Clones** it to your machine.
|
|
20
|
+
3. Sets up the **upstream remote**.
|
|
21
|
+
4. Creates a **fresh feature branch** linked to the issue.
|
|
54
22
|
|
|
55
|
-
|
|
56
|
-
|
|
23
|
+
### Submit your work
|
|
57
24
|
```bash
|
|
58
|
-
npx gitpadi
|
|
59
|
-
npx gitpadi prs merge 5 --method squash
|
|
60
|
-
npx gitpadi contributors score octocat
|
|
61
|
-
npx gitpadi repo create my-app --org MyOrg
|
|
25
|
+
npx gitpadi submit
|
|
62
26
|
```
|
|
27
|
+
When you're done, this command:
|
|
28
|
+
1. **Stages** all your changes.
|
|
29
|
+
2. Creates a **commit** with a clean message.
|
|
30
|
+
3. **Pushes** to your fork.
|
|
31
|
+
4. Opens a **Pull Request** on the original repo, auto-linked to the issue.
|
|
32
|
+
|
|
33
|
+
---
|
|
63
34
|
|
|
64
|
-
|
|
35
|
+
## š ļø For Maintainers
|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
<summary><b>š Issues</b></summary>
|
|
37
|
+
Manage your community and project health directly from your terminal.
|
|
68
38
|
|
|
39
|
+
### š Bulk Issue Creation
|
|
40
|
+
Create dozens of issues in seconds from a simple Markdown or JSON file.
|
|
69
41
|
```bash
|
|
70
|
-
npx gitpadi issues
|
|
71
|
-
npx gitpadi issues create -t "Bug fix" -l bug # Create issue
|
|
72
|
-
npx gitpadi issues bulk -f issues.json # Bulk create from JSON
|
|
73
|
-
npx gitpadi issues close 42 # Close issue
|
|
74
|
-
npx gitpadi issues reopen 42 # Reopen issue
|
|
75
|
-
npx gitpadi issues delete 42 # Close & lock
|
|
76
|
-
npx gitpadi issues assign 42 alice bob # Assign users
|
|
77
|
-
npx gitpadi issues assign-best 42 # š Auto-assign top scorer
|
|
78
|
-
npx gitpadi issues search "login bug" # Search issues
|
|
79
|
-
npx gitpadi issues label 42 bug priority-high # Add labels
|
|
42
|
+
npx gitpadi issues bulk -f roadmap.md
|
|
80
43
|
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<details>
|
|
84
|
-
<summary><b>š Pull Requests</b></summary>
|
|
44
|
+
*(Supports `## Heading` titles and `**Labels:**` tags).*
|
|
85
45
|
|
|
46
|
+
### š Smart Contributor Scoring
|
|
47
|
+
Auto-score applicants based on their GitHub history, account age, and relevance to the issue.
|
|
86
48
|
```bash
|
|
87
|
-
npx gitpadi
|
|
88
|
-
npx gitpadi prs merge 5 --method squash # Merge PR (squash/merge/rebase)
|
|
89
|
-
npx gitpadi prs close 5 # Close PR
|
|
90
|
-
npx gitpadi prs review 5 # Auto-review (size, tests, security)
|
|
91
|
-
npx gitpadi prs approve 5 # Approve PR
|
|
92
|
-
npx gitpadi prs diff 5 # Show changed files
|
|
49
|
+
npx gitpadi contributors score octocat
|
|
93
50
|
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<details>
|
|
97
|
-
<summary><b>š¦ Repositories</b></summary>
|
|
98
|
-
|
|
51
|
+
Or let GitPadi find the best applicant for an issue:
|
|
99
52
|
```bash
|
|
100
|
-
npx gitpadi
|
|
101
|
-
npx gitpadi repo create my-app --org MyOrg # Create repo
|
|
102
|
-
npx gitpadi repo delete my-app --org MyOrg # Delete repo
|
|
103
|
-
npx gitpadi repo clone my-app # Clone repo
|
|
104
|
-
npx gitpadi repo info my-app # Show repo details
|
|
105
|
-
npx gitpadi repo topics my-app rust blockchain # Set topics
|
|
53
|
+
npx gitpadi issues assign-best 42
|
|
106
54
|
```
|
|
107
|
-
</details>
|
|
108
55
|
|
|
109
|
-
|
|
110
|
-
|
|
56
|
+
### š Advanced PR Management
|
|
57
|
+
- **Review PRs**: Get an automated review (size analysis, security checks, test coverage).
|
|
58
|
+
- **Fast Merge**: Squash and merge PRs directly from the CLI.
|
|
59
|
+
- **CI Status**: View GitHub Action logs for a PR without opening a browser.
|
|
111
60
|
|
|
112
|
-
|
|
113
|
-
npx gitpadi contributors score octocat # Score a user (0-100)
|
|
114
|
-
npx gitpadi contributors rank 42 # Rank all applicants for issue #42
|
|
115
|
-
npx gitpadi contributors list # List repo contributors
|
|
116
|
-
```
|
|
117
|
-
</details>
|
|
61
|
+
---
|
|
118
62
|
|
|
119
|
-
|
|
120
|
-
|
|
63
|
+
## ā” Quick Start
|
|
64
|
+
|
|
65
|
+
No installation required. Just set your token and go:
|
|
121
66
|
|
|
122
67
|
```bash
|
|
123
|
-
|
|
124
|
-
npx gitpadi
|
|
125
|
-
npx gitpadi release list # List releases
|
|
68
|
+
export GITHUB_TOKEN=ghp_your_token
|
|
69
|
+
npx gitpadi
|
|
126
70
|
```
|
|
127
|
-
</details>
|
|
128
|
-
|
|
129
|
-
### Or install globally:
|
|
130
71
|
|
|
72
|
+
### Direct Commands
|
|
131
73
|
```bash
|
|
132
|
-
|
|
133
|
-
gitpadi
|
|
74
|
+
npx gitpadi issues list --limit 100
|
|
75
|
+
npx gitpadi prs review 5
|
|
76
|
+
npx gitpadi release create v1.0.0
|
|
134
77
|
```
|
|
135
78
|
|
|
136
79
|
---
|
|
137
80
|
|
|
138
|
-
## š§ Use as GitHub Action
|
|
139
|
-
|
|
140
|
-
Just add a workflow file ā no setup, no tokens to manage.
|
|
81
|
+
## š§ Use as a GitHub Action
|
|
141
82
|
|
|
142
|
-
|
|
83
|
+
GitPadi can run on every event in your repo. Just add a workflow file.
|
|
143
84
|
|
|
85
|
+
**Auto-Score Applicants:**
|
|
144
86
|
```yaml
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
issue_comment:
|
|
149
|
-
types: [created]
|
|
150
|
-
permissions:
|
|
151
|
-
contents: read
|
|
152
|
-
issues: write
|
|
153
|
-
pull-requests: read
|
|
154
|
-
jobs:
|
|
155
|
-
score:
|
|
156
|
-
if: "!github.event.issue.pull_request"
|
|
157
|
-
runs-on: ubuntu-latest
|
|
158
|
-
steps:
|
|
159
|
-
- uses: actions/checkout@v4
|
|
160
|
-
- uses: Netwalls/contributor-agent@v2
|
|
161
|
-
with:
|
|
162
|
-
action: score-applicant
|
|
163
|
-
notify-user: 'your-username' # ā you get @mentioned
|
|
87
|
+
- uses: Netwalls/contributor-agent@v2
|
|
88
|
+
with:
|
|
89
|
+
action: score-applicant
|
|
164
90
|
```
|
|
165
91
|
|
|
166
|
-
|
|
167
|
-
|
|
92
|
+
**Auto-Review PRs:**
|
|
168
93
|
```yaml
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
pull_request:
|
|
173
|
-
types: [opened, synchronize, reopened]
|
|
174
|
-
permissions:
|
|
175
|
-
contents: read
|
|
176
|
-
pull-requests: write
|
|
177
|
-
jobs:
|
|
178
|
-
review:
|
|
179
|
-
runs-on: ubuntu-latest
|
|
180
|
-
steps:
|
|
181
|
-
- uses: actions/checkout@v4
|
|
182
|
-
- uses: Netwalls/contributor-agent@v2
|
|
183
|
-
with:
|
|
184
|
-
action: review-pr
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### š Bulk Create Issues
|
|
188
|
-
|
|
189
|
-
```yaml
|
|
190
|
-
# .github/workflows/create-issues.yml
|
|
191
|
-
name: Create Issues
|
|
192
|
-
on:
|
|
193
|
-
workflow_dispatch:
|
|
194
|
-
inputs:
|
|
195
|
-
issues-file:
|
|
196
|
-
description: 'Path to issues JSON'
|
|
197
|
-
default: 'issues.json'
|
|
198
|
-
dry-run:
|
|
199
|
-
description: 'Preview only'
|
|
200
|
-
default: 'true'
|
|
201
|
-
permissions:
|
|
202
|
-
issues: write
|
|
203
|
-
jobs:
|
|
204
|
-
create:
|
|
205
|
-
runs-on: ubuntu-latest
|
|
206
|
-
steps:
|
|
207
|
-
- uses: actions/checkout@v4
|
|
208
|
-
- uses: Netwalls/contributor-agent@v2
|
|
209
|
-
with:
|
|
210
|
-
action: create-issues
|
|
211
|
-
issues-file: ${{ github.event.inputs.issues-file }}
|
|
212
|
-
dry-run: ${{ github.event.inputs.dry-run }}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## š Scoring Algorithm
|
|
218
|
-
|
|
219
|
-
GitPadi scores contributors on **6 dimensions** (100 points total):
|
|
220
|
-
|
|
221
|
-
| Category | Max | What It Measures |
|
|
222
|
-
|----------|-----|-----------------|
|
|
223
|
-
| š§ **Repo Experience** | **30** | Merged PRs, open PRs, issues in YOUR specific repo |
|
|
224
|
-
| šļø Account Maturity | 15 | Account age |
|
|
225
|
-
| š GitHub Presence | 15 | Public repos, followers, profile README |
|
|
226
|
-
| ā” Activity Level | 15 | Recent public GitHub activity |
|
|
227
|
-
| š Application Quality | 15 | Comment depth ā mentions approach, experience, timeline |
|
|
228
|
-
| š» Language Relevance | 10 | User's languages match issue label languages |
|
|
229
|
-
|
|
230
|
-
### Tiers
|
|
231
|
-
|
|
232
|
-
| Tier | Score | Recommendation |
|
|
233
|
-
|------|-------|---------------|
|
|
234
|
-
| š **S** | 75+ | Strong ā assign immediately |
|
|
235
|
-
| š¢ **A** | 55-74 | Good ā assign with confidence |
|
|
236
|
-
| š” **B** | 40-54 | Decent ā review profile first |
|
|
237
|
-
| š **C** | 25-39 | Below average ā wait for better |
|
|
238
|
-
| š“ **D** | 0-24 | Low ā manual review needed |
|
|
239
|
-
|
|
240
|
-
### Detected Application Phrases
|
|
241
|
-
|
|
242
|
-
> *"I'd like to work on this"* Ā· *"Can I take this?"* Ā· *"Assign me"* Ā· *"I'm interested"* Ā· *"I want to contribute"* Ā· *"Let me handle this"* Ā· *"Picking this up"* Ā· *"Claiming this"*
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## āļø Configuration
|
|
247
|
-
|
|
248
|
-
### Environment Variables
|
|
249
|
-
|
|
250
|
-
| Variable | Required | Description |
|
|
251
|
-
|----------|----------|-------------|
|
|
252
|
-
| `GITHUB_TOKEN` | ā
| GitHub PAT with `repo` scope |
|
|
253
|
-
| `GITHUB_OWNER` | For CLI | Org or username |
|
|
254
|
-
| `GITHUB_REPO` | For CLI | Repository name |
|
|
255
|
-
|
|
256
|
-
### CLI Flags
|
|
257
|
-
|
|
258
|
-
```bash
|
|
259
|
-
npx gitpadi --owner MyOrg --repo my-project --token ghp_xxx issues list
|
|
94
|
+
- uses: Netwalls/contributor-agent@v2
|
|
95
|
+
with:
|
|
96
|
+
action: review-pr
|
|
260
97
|
```
|
|
261
98
|
|
|
262
99
|
---
|
|
263
100
|
|
|
264
101
|
## š License
|
|
265
|
-
|
|
266
|
-
MIT ā Built by [Netwalls](https://github.com/Netwalls)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
i noticed that a users have more issues but it is just bring out the top 10 issues that are open how do we bring out more issues
|
|
102
|
+
MIT ā Built by [Netwalls](https://github.com/Netwalls)
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import inquirer from 'inquirer';
|
|
9
9
|
import gradient from 'gradient-string';
|
|
10
10
|
import figlet from 'figlet';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
11
12
|
import boxen from 'boxen';
|
|
12
13
|
import { createSpinner } from 'nanospinner';
|
|
13
14
|
import { Octokit } from '@octokit/rest';
|
|
@@ -257,16 +258,50 @@ async function contributorMenu() {
|
|
|
257
258
|
await contribute.viewLogs();
|
|
258
259
|
}
|
|
259
260
|
else if (action === 'submit') {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
262
|
+
const match = branch.match(/issue-(\d+)/);
|
|
263
|
+
const detectedIssue = match ? parseInt(match[1]) : undefined;
|
|
264
|
+
let detectedTitle = '';
|
|
265
|
+
if (detectedIssue) {
|
|
266
|
+
process.stdout.write(dim(` š Detected Issue #${detectedIssue} from branch... `));
|
|
267
|
+
try {
|
|
268
|
+
const { data: issueData } = await getOctokit().issues.get({
|
|
269
|
+
owner: getOwner(),
|
|
270
|
+
repo: getRepo(),
|
|
271
|
+
issue_number: detectedIssue,
|
|
272
|
+
});
|
|
273
|
+
detectedTitle = issueData.title;
|
|
274
|
+
console.log(cyan(`${detectedTitle}`));
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
console.log(dim(' (could not fetch title)'));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const { confirm } = await ask([
|
|
281
|
+
{
|
|
282
|
+
type: 'list',
|
|
283
|
+
name: 'confirm',
|
|
284
|
+
message: yellow('š Ready to submit?'),
|
|
285
|
+
choices: [
|
|
286
|
+
{ name: `ā
Yes, use automated metadata`, value: 'yes' },
|
|
287
|
+
{ name: `āļø Edit title/message manually`, value: 'edit' },
|
|
288
|
+
{ name: `ā¬
Back`, value: 'back' }
|
|
289
|
+
]
|
|
290
|
+
}
|
|
264
291
|
]);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
292
|
+
if (confirm === 'back')
|
|
293
|
+
return;
|
|
294
|
+
let submissionOpts = {};
|
|
295
|
+
if (confirm === 'edit') {
|
|
296
|
+
submissionOpts = await ask([
|
|
297
|
+
{ type: 'input', name: 'title', message: 'PR Title:', default: detectedTitle ? `fix: ${detectedTitle}` : '' },
|
|
298
|
+
{ type: 'input', name: 'message', message: 'Commit Message:', default: detectedTitle ? `fix: ${detectedTitle} (#${detectedIssue})` : '' },
|
|
299
|
+
{ type: 'input', name: 'issue', message: 'Issue #:', default: detectedIssue ? detectedIssue.toString() : '' }
|
|
300
|
+
]);
|
|
301
|
+
if (submissionOpts.issue)
|
|
302
|
+
submissionOpts.issue = parseInt(submissionOpts.issue);
|
|
303
|
+
}
|
|
304
|
+
await contribute.submitPR(submissionOpts);
|
|
270
305
|
}
|
|
271
306
|
}
|
|
272
307
|
}
|
|
@@ -363,11 +398,10 @@ async function issueMenu() {
|
|
|
363
398
|
else if (action === 'bulk') {
|
|
364
399
|
const a = await ask([
|
|
365
400
|
{ type: 'input', name: 'file', message: yellow('š Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
366
|
-
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
367
401
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
368
402
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
369
403
|
]);
|
|
370
|
-
await issues.createIssuesFromFile(a.file, {
|
|
404
|
+
await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
|
|
371
405
|
}
|
|
372
406
|
else if (action === 'assign-best') {
|
|
373
407
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
@@ -753,8 +787,8 @@ function setupCommander() {
|
|
|
753
787
|
.option('-i, --issue <n>', 'Issue number')
|
|
754
788
|
.action(async (o) => {
|
|
755
789
|
await contribute.submitPR({
|
|
756
|
-
title: o.title
|
|
757
|
-
message: o.message
|
|
790
|
+
title: o.title,
|
|
791
|
+
message: o.message,
|
|
758
792
|
issue: o.issue ? parseInt(o.issue) : undefined
|
|
759
793
|
});
|
|
760
794
|
});
|
|
@@ -81,8 +81,23 @@ export async function forkAndClone(target) {
|
|
|
81
81
|
execSync(`git remote add upstream https://github.com/${owner}/${repo}.git`, { stdio: 'pipe' });
|
|
82
82
|
}
|
|
83
83
|
catch { /* exists */ }
|
|
84
|
+
// 1. Detect default branch
|
|
85
|
+
let defaultBranch = 'main';
|
|
86
|
+
try {
|
|
87
|
+
execSync('git rev-parse upstream/main', { stdio: 'pipe' });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
defaultBranch = 'master';
|
|
91
|
+
}
|
|
92
|
+
// 2. Checkout default branch before syncing
|
|
93
|
+
try {
|
|
94
|
+
execSync(`git checkout ${defaultBranch}`, { stdio: 'pipe' });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// If it fails, maybe it's not even a valid default branch locally, but we'll try to sync anyway
|
|
98
|
+
}
|
|
84
99
|
await syncBranch();
|
|
85
|
-
// Create or switch to issue branch
|
|
100
|
+
// 3. Create or switch to issue branch
|
|
86
101
|
const branchName = issue ? `fix/issue-${issue}` : null;
|
|
87
102
|
if (branchName) {
|
|
88
103
|
try {
|
|
@@ -220,9 +235,34 @@ export async function submitPR(opts) {
|
|
|
220
235
|
const owner = getOwner();
|
|
221
236
|
const repo = getRepo();
|
|
222
237
|
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
238
|
+
// Auto-infer issue from branch name (e.g. fix/issue-303)
|
|
239
|
+
let linkedIssue = opts.issue;
|
|
240
|
+
if (!linkedIssue) {
|
|
241
|
+
const match = branch.match(/issue-(\d+)/);
|
|
242
|
+
if (match)
|
|
243
|
+
linkedIssue = parseInt(match[1]);
|
|
244
|
+
}
|
|
245
|
+
const octokit = getOctokit();
|
|
246
|
+
// 2. Fetch Issue Details for better PR Metadata
|
|
247
|
+
let issueTitle = '';
|
|
248
|
+
if (linkedIssue) {
|
|
249
|
+
spinner.text = `Fetching details for issue #${linkedIssue}...`;
|
|
250
|
+
try {
|
|
251
|
+
const { data: issueData } = await octokit.issues.get({
|
|
252
|
+
owner,
|
|
253
|
+
repo,
|
|
254
|
+
issue_number: linkedIssue,
|
|
255
|
+
});
|
|
256
|
+
issueTitle = issueData.title;
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
// Silently fallback if issue doesn't exist or no access
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const prTitle = opts.title || (issueTitle ? `fix: ${issueTitle} (#${linkedIssue})` : 'Automated contribution via GitPadi');
|
|
263
|
+
const commitMsg = opts.message || prTitle;
|
|
223
264
|
// 1. Stage and Commit
|
|
224
265
|
spinner.text = 'Staging and committing changes...';
|
|
225
|
-
const commitMsg = opts.message || opts.title || 'Automated contribution via GitPadi';
|
|
226
266
|
try {
|
|
227
267
|
execSync('git add .', { stdio: 'pipe' });
|
|
228
268
|
// Check if there are changes to commit
|
|
@@ -235,24 +275,25 @@ export async function submitPR(opts) {
|
|
|
235
275
|
// If commit fails (e.g. no changes), we might still want to push if there are unpushed commits
|
|
236
276
|
dim(' (Note: No new changes to commit or commit failed)');
|
|
237
277
|
}
|
|
238
|
-
// Auto-infer issue from branch name (e.g. fix/issue-303)
|
|
239
|
-
let linkedIssue = opts.issue;
|
|
240
|
-
if (!linkedIssue) {
|
|
241
|
-
const match = branch.match(/issue-(\d+)/);
|
|
242
|
-
if (match)
|
|
243
|
-
linkedIssue = parseInt(match[1]);
|
|
244
|
-
}
|
|
245
278
|
spinner.text = 'Pushing to your fork...';
|
|
246
279
|
execSync(`git push origin ${branch}`, { stdio: 'pipe' });
|
|
247
280
|
spinner.text = 'Creating Pull Request...';
|
|
248
281
|
const body = opts.body || (linkedIssue ? `Fixes #${linkedIssue}` : 'Automated PR via GitPadi');
|
|
249
|
-
|
|
282
|
+
// Detect base branch
|
|
283
|
+
let baseBranch = 'main';
|
|
284
|
+
try {
|
|
285
|
+
execSync('git rev-parse origin/main', { stdio: 'pipe' });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
baseBranch = 'master';
|
|
289
|
+
}
|
|
290
|
+
const { data: pr } = await octokit.pulls.create({
|
|
250
291
|
owner,
|
|
251
292
|
repo,
|
|
252
|
-
title:
|
|
293
|
+
title: prTitle,
|
|
253
294
|
body,
|
|
254
295
|
head: `${await getAuthenticatedUser()}:${branch}`,
|
|
255
|
-
base:
|
|
296
|
+
base: baseBranch,
|
|
256
297
|
});
|
|
257
298
|
spinner.succeed(`PR Created: ${green(pr.html_url)}`);
|
|
258
299
|
}
|
package/dist/commands/issues.js
CHANGED
|
@@ -11,16 +11,29 @@ export async function listIssues(opts) {
|
|
|
11
11
|
const octokit = getOctokit();
|
|
12
12
|
const spinner = ora(`Fetching issues from ${chalk.cyan(getFullRepo())}...`).start();
|
|
13
13
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const requestedLimit = opts.limit || 50;
|
|
15
|
+
const realIssues = [];
|
|
16
|
+
let page = 1;
|
|
17
|
+
// Fetch until we have enough real issues or run out of pages
|
|
18
|
+
while (realIssues.length < requestedLimit) {
|
|
19
|
+
const { data: issues } = await octokit.issues.listForRepo({
|
|
20
|
+
owner: getOwner(), repo: getRepo(),
|
|
21
|
+
state: opts.state || 'open',
|
|
22
|
+
labels: opts.labels || undefined,
|
|
23
|
+
per_page: 100, // Fetch max per page to be efficient
|
|
24
|
+
page: page++,
|
|
25
|
+
});
|
|
26
|
+
if (issues.length === 0)
|
|
27
|
+
break;
|
|
28
|
+
const batch = issues.filter((i) => !i.pull_request);
|
|
29
|
+
realIssues.push(...batch);
|
|
30
|
+
if (issues.length < 100)
|
|
31
|
+
break; // Last page
|
|
32
|
+
}
|
|
33
|
+
// Clip to requested limit
|
|
34
|
+
const finalIssues = realIssues.slice(0, requestedLimit);
|
|
22
35
|
spinner.stop();
|
|
23
|
-
if (
|
|
36
|
+
if (finalIssues.length === 0) {
|
|
24
37
|
console.log(chalk.yellow('\n No issues found.\n'));
|
|
25
38
|
return;
|
|
26
39
|
}
|
|
@@ -28,13 +41,13 @@ export async function listIssues(opts) {
|
|
|
28
41
|
head: ['#', 'Title', 'Labels', 'Assignee', 'State'].map((h) => chalk.cyan(h)),
|
|
29
42
|
style: { head: [], border: [] },
|
|
30
43
|
});
|
|
31
|
-
|
|
44
|
+
finalIssues.forEach((i) => {
|
|
32
45
|
const labels = i.labels.map((l) => typeof l === 'string' ? l : l.name || '').join(', ');
|
|
33
46
|
const assignee = i.assignee?.login || chalk.dim('unassigned');
|
|
34
47
|
const state = i.state === 'open' ? chalk.green('open') : chalk.red('closed');
|
|
35
48
|
table.push([`#${i.number}`, i.title.substring(0, 60), labels.substring(0, 30), assignee, state]);
|
|
36
49
|
});
|
|
37
|
-
console.log(`\n${chalk.bold(`š Issues ā ${getFullRepo()}`)} (${
|
|
50
|
+
console.log(`\n${chalk.bold(`š Issues ā ${getFullRepo()}`)} (${finalIssues.length})\n`);
|
|
38
51
|
console.log(table.toString());
|
|
39
52
|
console.log('');
|
|
40
53
|
}
|
|
@@ -137,16 +150,22 @@ export async function createIssuesFromFile(filePath, opts) {
|
|
|
137
150
|
console.log(`\n${chalk.bold('š GitPadi Issue Creator')}`);
|
|
138
151
|
console.log(chalk.dim(` Repo: ${getFullRepo()}`));
|
|
139
152
|
console.log(chalk.dim(` File: ${filePath} (${detectedFormat})`));
|
|
140
|
-
console.log(chalk.dim(`
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
console.log(chalk.dim(` Found: ${filtered.length} issues\n`));
|
|
154
|
+
// Always preview first
|
|
155
|
+
filtered.forEach((i) => {
|
|
156
|
+
console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
|
|
157
|
+
console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
|
|
158
|
+
});
|
|
159
|
+
// Ask for confirmation
|
|
160
|
+
const inquirer = (await import('inquirer')).default;
|
|
161
|
+
const { proceed } = await inquirer.prompt([{
|
|
162
|
+
type: 'confirm',
|
|
163
|
+
name: 'proceed',
|
|
164
|
+
message: chalk.yellow(`Create these ${filtered.length} issues on GitHub?`),
|
|
165
|
+
default: true,
|
|
166
|
+
}]);
|
|
167
|
+
if (!proceed) {
|
|
168
|
+
console.log(chalk.dim('\n Cancelled.\n'));
|
|
150
169
|
return;
|
|
151
170
|
}
|
|
152
171
|
const octokit = getOctokit();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitpadi",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.4",
|
|
4
|
+
"description": "GitPadi ā Your AI-powered GitHub management CLI. Create repos, manage issues & PRs, score contributors, and automate everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gitpadi": "./dist/cli.js"
|
package/src/cli.ts
CHANGED
|
@@ -287,16 +287,50 @@ async function contributorMenu() {
|
|
|
287
287
|
} else if (action === 'logs') {
|
|
288
288
|
await contribute.viewLogs();
|
|
289
289
|
} else if (action === 'submit') {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
290
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
291
|
+
const match = branch.match(/issue-(\d+)/);
|
|
292
|
+
const detectedIssue = match ? parseInt(match[1]) : undefined;
|
|
293
|
+
|
|
294
|
+
let detectedTitle = '';
|
|
295
|
+
if (detectedIssue) {
|
|
296
|
+
process.stdout.write(dim(` š Detected Issue #${detectedIssue} from branch... `));
|
|
297
|
+
try {
|
|
298
|
+
const { data: issueData } = await getOctokit().issues.get({
|
|
299
|
+
owner: getOwner(),
|
|
300
|
+
repo: getRepo(),
|
|
301
|
+
issue_number: detectedIssue,
|
|
302
|
+
});
|
|
303
|
+
detectedTitle = issueData.title;
|
|
304
|
+
console.log(cyan(`${detectedTitle}`));
|
|
305
|
+
} catch { console.log(dim(' (could not fetch title)')); }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const { confirm } = await ask([
|
|
309
|
+
{
|
|
310
|
+
type: 'list',
|
|
311
|
+
name: 'confirm',
|
|
312
|
+
message: yellow('š Ready to submit?'),
|
|
313
|
+
choices: [
|
|
314
|
+
{ name: `ā
Yes, use automated metadata`, value: 'yes' },
|
|
315
|
+
{ name: `āļø Edit title/message manually`, value: 'edit' },
|
|
316
|
+
{ name: `ā¬
Back`, value: 'back' }
|
|
317
|
+
]
|
|
318
|
+
}
|
|
294
319
|
]);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
320
|
+
|
|
321
|
+
if (confirm === 'back') return;
|
|
322
|
+
|
|
323
|
+
let submissionOpts: any = {};
|
|
324
|
+
if (confirm === 'edit') {
|
|
325
|
+
submissionOpts = await ask([
|
|
326
|
+
{ type: 'input', name: 'title', message: 'PR Title:', default: detectedTitle ? `fix: ${detectedTitle}` : '' },
|
|
327
|
+
{ type: 'input', name: 'message', message: 'Commit Message:', default: detectedTitle ? `fix: ${detectedTitle} (#${detectedIssue})` : '' },
|
|
328
|
+
{ type: 'input', name: 'issue', message: 'Issue #:', default: detectedIssue ? detectedIssue.toString() : '' }
|
|
329
|
+
]);
|
|
330
|
+
if (submissionOpts.issue) submissionOpts.issue = parseInt(submissionOpts.issue);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await contribute.submitPR(submissionOpts);
|
|
300
334
|
}
|
|
301
335
|
}
|
|
302
336
|
}
|
|
@@ -393,11 +427,10 @@ async function issueMenu() {
|
|
|
393
427
|
} else if (action === 'bulk') {
|
|
394
428
|
const a = await ask([
|
|
395
429
|
{ type: 'input', name: 'file', message: yellow('š Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
396
|
-
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
397
430
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
398
431
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
399
432
|
]);
|
|
400
|
-
await issues.createIssuesFromFile(a.file, {
|
|
433
|
+
await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
|
|
401
434
|
} else if (action === 'assign-best') {
|
|
402
435
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
403
436
|
const octokit = getOctokit();
|
|
@@ -785,8 +818,8 @@ function setupCommander(): Command {
|
|
|
785
818
|
.option('-i, --issue <n>', 'Issue number')
|
|
786
819
|
.action(async (o) => {
|
|
787
820
|
await contribute.submitPR({
|
|
788
|
-
title: o.title
|
|
789
|
-
message: o.message
|
|
821
|
+
title: o.title,
|
|
822
|
+
message: o.message,
|
|
790
823
|
issue: o.issue ? parseInt(o.issue) : undefined
|
|
791
824
|
});
|
|
792
825
|
});
|
|
@@ -100,9 +100,24 @@ export async function forkAndClone(target: string) {
|
|
|
100
100
|
// Ensure upstream remote exists
|
|
101
101
|
try { execSync(`git remote add upstream https://github.com/${owner}/${repo}.git`, { stdio: 'pipe' }); } catch { /* exists */ }
|
|
102
102
|
|
|
103
|
+
// 1. Detect default branch
|
|
104
|
+
let defaultBranch = 'main';
|
|
105
|
+
try {
|
|
106
|
+
execSync('git rev-parse upstream/main', { stdio: 'pipe' });
|
|
107
|
+
} catch {
|
|
108
|
+
defaultBranch = 'master';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2. Checkout default branch before syncing
|
|
112
|
+
try {
|
|
113
|
+
execSync(`git checkout ${defaultBranch}`, { stdio: 'pipe' });
|
|
114
|
+
} catch {
|
|
115
|
+
// If it fails, maybe it's not even a valid default branch locally, but we'll try to sync anyway
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
await syncBranch();
|
|
104
119
|
|
|
105
|
-
// Create or switch to issue branch
|
|
120
|
+
// 3. Create or switch to issue branch
|
|
106
121
|
const branchName = issue ? `fix/issue-${issue}` : null;
|
|
107
122
|
if (branchName) {
|
|
108
123
|
try {
|
|
@@ -253,9 +268,36 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
|
|
|
253
268
|
const repo = getRepo();
|
|
254
269
|
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
255
270
|
|
|
271
|
+
// Auto-infer issue from branch name (e.g. fix/issue-303)
|
|
272
|
+
let linkedIssue = opts.issue;
|
|
273
|
+
if (!linkedIssue) {
|
|
274
|
+
const match = branch.match(/issue-(\d+)/);
|
|
275
|
+
if (match) linkedIssue = parseInt(match[1]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const octokit = getOctokit();
|
|
279
|
+
|
|
280
|
+
// 2. Fetch Issue Details for better PR Metadata
|
|
281
|
+
let issueTitle = '';
|
|
282
|
+
if (linkedIssue) {
|
|
283
|
+
spinner.text = `Fetching details for issue #${linkedIssue}...`;
|
|
284
|
+
try {
|
|
285
|
+
const { data: issueData } = await octokit.issues.get({
|
|
286
|
+
owner,
|
|
287
|
+
repo,
|
|
288
|
+
issue_number: linkedIssue,
|
|
289
|
+
});
|
|
290
|
+
issueTitle = issueData.title;
|
|
291
|
+
} catch (e) {
|
|
292
|
+
// Silently fallback if issue doesn't exist or no access
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const prTitle = opts.title || (issueTitle ? `fix: ${issueTitle} (#${linkedIssue})` : 'Automated contribution via GitPadi');
|
|
297
|
+
const commitMsg = opts.message || prTitle;
|
|
298
|
+
|
|
256
299
|
// 1. Stage and Commit
|
|
257
300
|
spinner.text = 'Staging and committing changes...';
|
|
258
|
-
const commitMsg = opts.message || opts.title || 'Automated contribution via GitPadi';
|
|
259
301
|
|
|
260
302
|
try {
|
|
261
303
|
execSync('git add .', { stdio: 'pipe' });
|
|
@@ -269,26 +311,27 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
|
|
|
269
311
|
dim(' (Note: No new changes to commit or commit failed)');
|
|
270
312
|
}
|
|
271
313
|
|
|
272
|
-
// Auto-infer issue from branch name (e.g. fix/issue-303)
|
|
273
|
-
let linkedIssue = opts.issue;
|
|
274
|
-
if (!linkedIssue) {
|
|
275
|
-
const match = branch.match(/issue-(\d+)/);
|
|
276
|
-
if (match) linkedIssue = parseInt(match[1]);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
314
|
spinner.text = 'Pushing to your fork...';
|
|
280
315
|
execSync(`git push origin ${branch}`, { stdio: 'pipe' });
|
|
281
316
|
|
|
282
317
|
spinner.text = 'Creating Pull Request...';
|
|
283
318
|
const body = opts.body || (linkedIssue ? `Fixes #${linkedIssue}` : 'Automated PR via GitPadi');
|
|
284
319
|
|
|
285
|
-
|
|
320
|
+
// Detect base branch
|
|
321
|
+
let baseBranch = 'main';
|
|
322
|
+
try {
|
|
323
|
+
execSync('git rev-parse origin/main', { stdio: 'pipe' });
|
|
324
|
+
} catch {
|
|
325
|
+
baseBranch = 'master';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const { data: pr } = await octokit.pulls.create({
|
|
286
329
|
owner,
|
|
287
330
|
repo,
|
|
288
|
-
title:
|
|
331
|
+
title: prTitle,
|
|
289
332
|
body,
|
|
290
333
|
head: `${await getAuthenticatedUser()}:${branch}`,
|
|
291
|
-
base:
|
|
334
|
+
base: baseBranch,
|
|
292
335
|
});
|
|
293
336
|
|
|
294
337
|
spinner.succeed(`PR Created: ${green(pr.html_url)}`);
|
package/src/commands/issues.ts
CHANGED
|
@@ -14,18 +14,34 @@ export async function listIssues(opts: { state?: string; labels?: string; limit?
|
|
|
14
14
|
const spinner = ora(`Fetching issues from ${chalk.cyan(getFullRepo())}...`).start();
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const requestedLimit = opts.limit || 50;
|
|
18
|
+
const realIssues: any[] = [];
|
|
19
|
+
let page = 1;
|
|
20
|
+
|
|
21
|
+
// Fetch until we have enough real issues or run out of pages
|
|
22
|
+
while (realIssues.length < requestedLimit) {
|
|
23
|
+
const { data: issues } = await octokit.issues.listForRepo({
|
|
24
|
+
owner: getOwner(), repo: getRepo(),
|
|
25
|
+
state: (opts.state as 'open' | 'closed' | 'all') || 'open',
|
|
26
|
+
labels: opts.labels || undefined,
|
|
27
|
+
per_page: 100, // Fetch max per page to be efficient
|
|
28
|
+
page: page++,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (issues.length === 0) break;
|
|
32
|
+
|
|
33
|
+
const batch = issues.filter((i) => !i.pull_request);
|
|
34
|
+
realIssues.push(...batch);
|
|
35
|
+
|
|
36
|
+
if (issues.length < 100) break; // Last page
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Clip to requested limit
|
|
40
|
+
const finalIssues = realIssues.slice(0, requestedLimit);
|
|
23
41
|
|
|
24
|
-
// Filter out PRs (GitHub API returns PRs in issues endpoint)
|
|
25
|
-
const realIssues = issues.filter((i) => !i.pull_request);
|
|
26
42
|
spinner.stop();
|
|
27
43
|
|
|
28
|
-
if (
|
|
44
|
+
if (finalIssues.length === 0) {
|
|
29
45
|
console.log(chalk.yellow('\n No issues found.\n'));
|
|
30
46
|
return;
|
|
31
47
|
}
|
|
@@ -35,14 +51,14 @@ export async function listIssues(opts: { state?: string; labels?: string; limit?
|
|
|
35
51
|
style: { head: [], border: [] },
|
|
36
52
|
});
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
const labels = i.labels.map((l) => typeof l === 'string' ? l : l.name || '').join(', ');
|
|
54
|
+
finalIssues.forEach((i) => {
|
|
55
|
+
const labels = i.labels.map((l: any) => typeof l === 'string' ? l : l.name || '').join(', ');
|
|
40
56
|
const assignee = i.assignee?.login || chalk.dim('unassigned');
|
|
41
57
|
const state = i.state === 'open' ? chalk.green('open') : chalk.red('closed');
|
|
42
58
|
table.push([`#${i.number}`, i.title.substring(0, 60), labels.substring(0, 30), assignee, state]);
|
|
43
59
|
});
|
|
44
60
|
|
|
45
|
-
console.log(`\n${chalk.bold(`š Issues ā ${getFullRepo()}`)} (${
|
|
61
|
+
console.log(`\n${chalk.bold(`š Issues ā ${getFullRepo()}`)} (${finalIssues.length})\n`);
|
|
46
62
|
console.log(table.toString());
|
|
47
63
|
console.log('');
|
|
48
64
|
} catch (e: any) {
|
|
@@ -153,16 +169,25 @@ export async function createIssuesFromFile(filePath: string, opts: { dryRun?: bo
|
|
|
153
169
|
console.log(`\n${chalk.bold('š GitPadi Issue Creator')}`);
|
|
154
170
|
console.log(chalk.dim(` Repo: ${getFullRepo()}`));
|
|
155
171
|
console.log(chalk.dim(` File: ${filePath} (${detectedFormat})`));
|
|
156
|
-
console.log(chalk.dim(`
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
172
|
+
console.log(chalk.dim(` Found: ${filtered.length} issues\n`));
|
|
173
|
+
|
|
174
|
+
// Always preview first
|
|
175
|
+
filtered.forEach((i: any) => {
|
|
176
|
+
console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
|
|
177
|
+
console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Ask for confirmation
|
|
181
|
+
const inquirer = (await import('inquirer')).default;
|
|
182
|
+
const { proceed } = await inquirer.prompt([{
|
|
183
|
+
type: 'confirm',
|
|
184
|
+
name: 'proceed',
|
|
185
|
+
message: chalk.yellow(`Create these ${filtered.length} issues on GitHub?`),
|
|
186
|
+
default: true,
|
|
187
|
+
}]);
|
|
188
|
+
|
|
189
|
+
if (!proceed) {
|
|
190
|
+
console.log(chalk.dim('\n Cancelled.\n'));
|
|
166
191
|
return;
|
|
167
192
|
}
|
|
168
193
|
|