gitpadi 2.0.5 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import { execSync } from 'child_process';
12
12
  import boxen from 'boxen';
13
13
  import { createSpinner } from 'nanospinner';
14
14
  import { Octokit } from '@octokit/rest';
15
- import { initGitHub, setRepo, getOwner, getRepo, getOctokit, saveConfig, getToken } from './core/github.js';
15
+ import { initGitHub, setRepo, getOwner, getRepo, getOctokit, saveConfig, getToken, getAuthenticatedUser } from './core/github.js';
16
16
  import * as issues from './commands/issues.js';
17
17
  import * as prs from './commands/prs.js';
18
18
  import * as repos from './commands/repos.js';
@@ -157,35 +157,63 @@ async function ensureAuthenticated() {
157
157
  /**
158
158
  * Ensures we have a target repository (Owner/Repo)
159
159
  */
160
- async function ensureTargetRepo() {
160
+ async function ensureTargetRepo(force = false) {
161
161
  let owner = getOwner();
162
162
  let repo = getRepo();
163
- if (owner && repo) {
163
+ if (!force && owner && repo) {
164
164
  return;
165
165
  }
166
166
  console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
167
- const answers = await inquirer.prompt([
168
- {
167
+ // 1. Get the Owner/Org
168
+ const { targetOwner } = await inquirer.prompt([{
169
169
  type: 'input',
170
- name: 'owner',
170
+ name: 'targetOwner',
171
171
  message: cyan('👤 GitHub Owner/Org:'),
172
- default: owner || '',
172
+ default: owner || await getAuthenticatedUser(),
173
173
  validate: (v) => v.length > 0 || 'Required',
174
- },
175
- {
176
- type: 'input',
177
- name: 'repo',
178
- message: cyan('📦 Repository name:'),
179
- default: repo || '',
180
- validate: (v) => v.length > 0 || 'Required',
181
- },
182
- ]);
183
- setRepo(answers.owner, answers.repo);
184
- saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
185
- const spinner = createSpinner(dim('Connecting...')).start();
186
- await sleep(400);
187
- spinner.success({ text: green(`Locked in ${cyan(`${answers.owner}/${answers.repo}`)}`) });
188
- console.log('');
174
+ }]);
175
+ // 2. Fetch Repos for that Owner
176
+ const spin = createSpinner(dim(`Fetching repositories for ${targetOwner}...`)).start();
177
+ const fetchedRepos = await repos.listRepos({ owner: targetOwner, silent: true });
178
+ if (fetchedRepos.length > 0) {
179
+ spin.success({ text: green(`Found ${fetchedRepos.length} repositories for ${targetOwner}`) });
180
+ const { selectedRepo } = await inquirer.prompt([{
181
+ type: 'list',
182
+ name: 'selectedRepo',
183
+ message: cyan('📦 Select Repository:'),
184
+ choices: [
185
+ ...fetchedRepos.map((r) => ({ name: r.name, value: r.name })),
186
+ new inquirer.Separator(),
187
+ { name: '✍️ Enter manually...', value: '__manual__' }
188
+ ],
189
+ loop: false
190
+ }]);
191
+ if (selectedRepo !== '__manual__') {
192
+ repo = selectedRepo;
193
+ }
194
+ else {
195
+ const { manualRepo } = await inquirer.prompt([{
196
+ type: 'input',
197
+ name: 'manualRepo',
198
+ message: cyan('📦 Repository name:'),
199
+ validate: (v) => v.length > 0 || 'Required',
200
+ }]);
201
+ repo = manualRepo;
202
+ }
203
+ }
204
+ else {
205
+ spin.warn({ text: yellow(`No public repositories found for ${targetOwner}.`) });
206
+ const { manualRepo } = await inquirer.prompt([{
207
+ type: 'input',
208
+ name: 'manualRepo',
209
+ message: cyan('📦 Enter Repository name manually:'),
210
+ validate: (v) => v.length > 0 || 'Required',
211
+ }]);
212
+ repo = manualRepo;
213
+ }
214
+ setRepo(targetOwner, repo);
215
+ saveConfig({ token: getToken(), owner: targetOwner, repo: repo });
216
+ console.log(green(`\n ✅ Locked in → ${cyan(`${targetOwner}/${repo}`)}\n`));
189
217
  }
190
218
  // ── Mode Selector ──────────────────────────────────────────────────────
191
219
  async function mainMenu() {
@@ -333,16 +361,7 @@ async function maintainerMenu() {
333
361
  if (category === 'back')
334
362
  break;
335
363
  if (category === 'switch') {
336
- await safeMenu(async () => {
337
- const a = await inquirer.prompt([
338
- { type: 'input', name: 'owner', message: cyan('👤 New Owner/Org:'), default: getOwner() },
339
- { type: 'input', name: 'repo', message: cyan('📦 New Repo:'), default: getRepo() },
340
- ]);
341
- setRepo(a.owner, a.repo);
342
- const s = createSpinner(dim('Switching...')).start();
343
- await sleep(400);
344
- s.success({ text: green(`Now targeting ${cyan(`${a.owner}/${a.repo}`)}`) });
345
- });
364
+ await ensureTargetRepo(true);
346
365
  continue;
347
366
  }
348
367
  if (category === 'issues')
@@ -91,9 +91,23 @@ export async function listRepos(opts) {
91
91
  const spinner = !opts.silent ? ora('Fetching repos...').start() : null;
92
92
  const octokit = getOctokit();
93
93
  try {
94
- let repos;
95
- if (opts.org) {
96
- ({ data: repos } = await octokit.repos.listForOrg({ org: opts.org, per_page: opts.limit || 100, sort: 'updated' }));
94
+ let repos = [];
95
+ const owner = opts.org || opts.owner;
96
+ if (owner) {
97
+ // Check if it's an org or user
98
+ try {
99
+ const { data: entity } = await octokit.users.getByUsername({ username: owner });
100
+ if (entity.type === 'Organization') {
101
+ ({ data: repos } = await octokit.repos.listForOrg({ org: owner, per_page: opts.limit || 100, sort: 'updated' }));
102
+ }
103
+ else {
104
+ ({ data: repos } = await octokit.repos.listForUser({ username: owner, per_page: opts.limit || 100, sort: 'updated' }));
105
+ }
106
+ }
107
+ catch {
108
+ // Fallback to org if lookup fails (might be a private org only visible to token)
109
+ ({ data: repos } = await octokit.repos.listForOrg({ org: owner, per_page: opts.limit || 100, sort: 'updated' }));
110
+ }
97
111
  }
98
112
  else {
99
113
  ({ data: repos } = await octokit.repos.listForAuthenticatedUser({ per_page: opts.limit || 100, sort: 'updated' }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitpadi",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
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": {
package/src/cli.ts CHANGED
@@ -15,7 +15,7 @@ import { execSync } from 'child_process';
15
15
  import boxen from 'boxen';
16
16
  import { createSpinner } from 'nanospinner';
17
17
  import { Octokit } from '@octokit/rest';
18
- import { initGitHub, setRepo, getOwner, getRepo, getOctokit, loadConfig, saveConfig, getToken } from './core/github.js';
18
+ import { initGitHub, setRepo, getOwner, getRepo, getOctokit, loadConfig, saveConfig, getToken, getAuthenticatedUser } from './core/github.js';
19
19
 
20
20
  import * as issues from './commands/issues.js';
21
21
  import * as prs from './commands/prs.js';
@@ -178,40 +178,70 @@ async function ensureAuthenticated() {
178
178
  /**
179
179
  * Ensures we have a target repository (Owner/Repo)
180
180
  */
181
- async function ensureTargetRepo() {
181
+ async function ensureTargetRepo(force = false) {
182
182
  let owner = getOwner();
183
183
  let repo = getRepo();
184
184
 
185
- if (owner && repo) {
185
+ if (!force && owner && repo) {
186
186
  return;
187
187
  }
188
188
 
189
189
  console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
190
190
 
191
- const answers = await inquirer.prompt([
192
- {
193
- type: 'input',
194
- name: 'owner',
195
- message: cyan('👤 GitHub Owner/Org:'),
196
- default: owner || '',
197
- validate: (v: string) => v.length > 0 || 'Required',
198
- },
199
- {
191
+ // 1. Get the Owner/Org
192
+ const { targetOwner } = await inquirer.prompt([{
193
+ type: 'input',
194
+ name: 'targetOwner',
195
+ message: cyan('👤 GitHub Owner/Org:'),
196
+ default: owner || await getAuthenticatedUser(),
197
+ validate: (v: string) => v.length > 0 || 'Required',
198
+ }]);
199
+
200
+ // 2. Fetch Repos for that Owner
201
+ const spin = createSpinner(dim(`Fetching repositories for ${targetOwner}...`)).start();
202
+ const fetchedRepos = await repos.listRepos({ owner: targetOwner, silent: true });
203
+
204
+ if (fetchedRepos.length > 0) {
205
+ spin.success({ text: green(`Found ${fetchedRepos.length} repositories for ${targetOwner}`) });
206
+
207
+ const { selectedRepo } = await inquirer.prompt([{
208
+ type: 'list',
209
+ name: 'selectedRepo',
210
+ message: cyan('📦 Select Repository:'),
211
+ choices: [
212
+ ...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name })),
213
+ new inquirer.Separator(),
214
+ { name: '✍️ Enter manually...', value: '__manual__' }
215
+ ],
216
+ loop: false
217
+ }]);
218
+
219
+ if (selectedRepo !== '__manual__') {
220
+ repo = selectedRepo;
221
+ } else {
222
+ const { manualRepo } = await inquirer.prompt([{
223
+ type: 'input',
224
+ name: 'manualRepo',
225
+ message: cyan('📦 Repository name:'),
226
+ validate: (v: string) => v.length > 0 || 'Required',
227
+ }]);
228
+ repo = manualRepo;
229
+ }
230
+ } else {
231
+ spin.warn({ text: yellow(`No public repositories found for ${targetOwner}.`) });
232
+ const { manualRepo } = await inquirer.prompt([{
200
233
  type: 'input',
201
- name: 'repo',
202
- message: cyan('📦 Repository name:'),
203
- default: repo || '',
234
+ name: 'manualRepo',
235
+ message: cyan('📦 Enter Repository name manually:'),
204
236
  validate: (v: string) => v.length > 0 || 'Required',
205
- },
206
- ]);
237
+ }]);
238
+ repo = manualRepo;
239
+ }
207
240
 
208
- setRepo(answers.owner, answers.repo);
209
- saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
241
+ setRepo(targetOwner, repo as string);
242
+ saveConfig({ token: getToken(), owner: targetOwner, repo: repo as string });
210
243
 
211
- const spinner = createSpinner(dim('Connecting...')).start();
212
- await sleep(400);
213
- spinner.success({ text: green(`Locked in → ${cyan(`${answers.owner}/${answers.repo}`)}`) });
214
- console.log('');
244
+ console.log(green(`\n ✅ Locked in → ${cyan(`${targetOwner}/${repo}`)}\n`));
215
245
  }
216
246
 
217
247
  // ── Mode Selector ──────────────────────────────────────────────────────
@@ -365,16 +395,7 @@ async function maintainerMenu() {
365
395
  if (category === 'back') break;
366
396
 
367
397
  if (category === 'switch') {
368
- await safeMenu(async () => {
369
- const a = await inquirer.prompt([
370
- { type: 'input', name: 'owner', message: cyan('👤 New Owner/Org:'), default: getOwner() },
371
- { type: 'input', name: 'repo', message: cyan('📦 New Repo:'), default: getRepo() },
372
- ]);
373
- setRepo(a.owner, a.repo);
374
- const s = createSpinner(dim('Switching...')).start();
375
- await sleep(400);
376
- s.success({ text: green(`Now targeting ${cyan(`${a.owner}/${a.repo}`)}`) });
377
- });
398
+ await ensureTargetRepo(true);
378
399
  continue;
379
400
  }
380
401
 
@@ -101,14 +101,27 @@ export async function setTopics(name: string, topics: string[], opts: { org?: st
101
101
  } catch (e: any) { spinner.fail(e.message); }
102
102
  }
103
103
 
104
- export async function listRepos(opts: { org?: string; limit?: number; silent?: boolean }) {
104
+ export async function listRepos(opts: { org?: string; owner?: string; limit?: number; silent?: boolean }) {
105
105
  const spinner = !opts.silent ? ora('Fetching repos...').start() : null;
106
106
  const octokit = getOctokit();
107
107
 
108
108
  try {
109
- let repos: any[];
110
- if (opts.org) {
111
- ({ data: repos } = await octokit.repos.listForOrg({ org: opts.org, per_page: opts.limit || 100, sort: 'updated' }));
109
+ let repos: any[] = [];
110
+ const owner = opts.org || opts.owner;
111
+
112
+ if (owner) {
113
+ // Check if it's an org or user
114
+ try {
115
+ const { data: entity } = await octokit.users.getByUsername({ username: owner });
116
+ if (entity.type === 'Organization') {
117
+ ({ data: repos } = await octokit.repos.listForOrg({ org: owner, per_page: opts.limit || 100, sort: 'updated' }));
118
+ } else {
119
+ ({ data: repos } = await octokit.repos.listForUser({ username: owner, per_page: opts.limit || 100, sort: 'updated' }));
120
+ }
121
+ } catch {
122
+ // Fallback to org if lookup fails (might be a private org only visible to token)
123
+ ({ data: repos } = await octokit.repos.listForOrg({ org: owner, per_page: opts.limit || 100, sort: 'updated' }));
124
+ }
112
125
  } else {
113
126
  ({ data: repos } = await octokit.repos.listForAuthenticatedUser({ per_page: opts.limit || 100, sort: 'updated' }));
114
127
  }