gh-manager-cli 1.10.6 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +76 -39
- package/dist/{chunk-XCFI3TG5.js → chunk-75Y3BQBE.js} +16 -10
- package/dist/{github-6IFMCQKW.js → github-6OEBCAUZ.js} +1 -1
- package/dist/index.js +789 -361
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.11.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.10.6...v1.11.0) (2025-09-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add modal-based visibility filtering and sort selection ([1c72224](https://github.com/wiiiimm/gh-manager-cli/commit/1c72224b4854f69481bb21f089a926dc2c18e53f))
|
|
7
|
+
|
|
1
8
|
## [1.10.6](https://github.com/wiiiimm/gh-manager-cli/compare/v1.10.5...v1.10.6) (2025-09-01)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -10,25 +10,20 @@
|
|
|
10
10
|
|
|
11
11
|
Interactive terminal app to browse and manage your personal GitHub repositories. Built with Ink (React for CLIs) and the GitHub GraphQL API.
|
|
12
12
|
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="docs/demo_interactive.gif" alt="Interactive demo of gh-manager-cli" width="900" />
|
|
15
|
+
<br />
|
|
16
|
+
<em>Fast, keyboard-first GitHub repo management from your terminal</em>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
13
19
|
## Screenshots
|
|
14
20
|
|
|
15
21
|
<div align="center">
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
### Authentication Flow
|
|
23
|
-
Secure GitHub token authentication with validation and persistence.
|
|
24
|
-
|
|
25
|
-
<img src="docs/demo_login.png" alt="Login screen prompting for GitHub token" width="800">
|
|
26
|
-
|
|
27
|
-
### Delete Confirmation
|
|
28
|
-
Safe repository deletion with two-step confirmation process.
|
|
29
|
-
|
|
30
|
-
<img src="docs/demo_delete_confirmation.png" alt="Delete confirmation dialog with security prompts" width="800">
|
|
31
|
-
|
|
22
|
+
<img src="docs/demo_repo_listing.png" alt="Repository listing with metadata" width="31%" />
|
|
23
|
+
<img src="docs/demo_login.png" alt="GitHub token authentication flow" width="31%" />
|
|
24
|
+
<img src="docs/demo_delete_confirmation.png" alt="Two-step delete confirmation" width="31%" />
|
|
25
|
+
<br />
|
|
26
|
+
<sub>Listing • Auth • Delete confirmation</sub>
|
|
32
27
|
</div>
|
|
33
28
|
|
|
34
29
|
## Quick Start
|
|
@@ -46,28 +41,34 @@ On first run, you'll be prompted for a GitHub Personal Access Token.
|
|
|
46
41
|
- **Token Authentication**: Secure PAT storage with validation and persistence
|
|
47
42
|
- **Repository Listing**: Browse all your personal repositories with metadata (stars, forks, language, etc.)
|
|
48
43
|
- **Live Pagination**: Infinite scroll with automatic page prefetching
|
|
49
|
-
- **
|
|
44
|
+
- **Interactive Sorting**: Modal-based sort selection (updated, pushed, name, stars) with direction toggle
|
|
50
45
|
- **Smart Search**: Server-side search through repository names and descriptions (3+ characters)
|
|
46
|
+
- **Visibility Filtering**: Modal-based visibility filter (All, Public, Private, Internal for enterprise) with server-side filtering
|
|
47
|
+
- **Fork Status Tracking**: Toggle display of commits behind upstream for forked repositories
|
|
51
48
|
- **Repository Actions**:
|
|
52
49
|
- View detailed info (`I`) - Shows repository metadata, language, size, and timestamps
|
|
53
50
|
- Open in browser (Enter/`O`)
|
|
54
|
-
- Delete repository (`Del` or `
|
|
51
|
+
- Delete repository (`Del` or `Backspace`) with secure two-step confirmation
|
|
55
52
|
- Archive/unarchive repositories (`Ctrl+A`) with confirmation prompts
|
|
56
53
|
- Sync forks with upstream (`Ctrl+S`) with automatic conflict detection
|
|
57
54
|
|
|
58
55
|
### User Interface & Experience
|
|
59
56
|
- **Keyboard Navigation**: Full keyboard control (arrow keys, PageUp/Down, `Ctrl+G`/`G`)
|
|
60
57
|
- **Display Density**: Toggle between compact/cozy/comfy spacing (`T`)
|
|
61
|
-
- **Visual Indicators**: Fork status, private/archived badges, language colors
|
|
58
|
+
- **Visual Indicators**: Fork status, private/archived badges, language colors, visibility status
|
|
59
|
+
- **Interactive Modals**: Sort selection, visibility filtering, and organization switching via modal dialogs
|
|
60
|
+
- **Balanced Layout**: Repository items with spacing above and below for better visual hierarchy
|
|
62
61
|
- **Loading States**: Contextual loading screens for sorting and refreshing operations
|
|
63
62
|
- **Rate Limit Monitoring**: Live API usage display with visual warnings
|
|
63
|
+
- **Improved Layout**: Balanced spacing above and below repository items for better visual hierarchy
|
|
64
64
|
|
|
65
65
|
### Technical Features
|
|
66
|
-
- **Preference Persistence**: UI settings (sort, density) saved between sessions
|
|
66
|
+
- **Preference Persistence**: UI settings (sort, density, visibility filter, fork tracking) saved between sessions
|
|
67
|
+
- **Server-side Filtering**: Visibility filtering performed at GitHub API level for accurate pagination
|
|
67
68
|
- **Cross-platform**: Works on macOS, Linux, and Windows
|
|
68
69
|
- **Secure Storage**: Token stored with proper file permissions (0600)
|
|
69
70
|
- **Error Handling**: Graceful error recovery with retry mechanisms
|
|
70
|
-
- **Performance**: Efficient GraphQL queries with virtualized rendering
|
|
71
|
+
- **Performance**: Efficient GraphQL queries with virtualized rendering and server-side filtering
|
|
71
72
|
|
|
72
73
|
## Installation
|
|
73
74
|
|
|
@@ -139,31 +140,65 @@ The app needs a GitHub token to read your repositories.
|
|
|
139
140
|
|
|
140
141
|
Note: Tokens are stored in plaintext on disk with restricted permissions. Future work may add OS keychain support.
|
|
141
142
|
|
|
143
|
+
### PAT Permissions & Scopes
|
|
144
|
+
|
|
145
|
+
Choose the least-privileged token for the features you plan to use:
|
|
146
|
+
|
|
147
|
+
- Browsing/searching repos (public only): `public_repo`
|
|
148
|
+
- Browsing/searching repos (includes private): `repo`
|
|
149
|
+
- Archive/Unarchive repository: `repo` (and you must have admin or maintainer rights on the repo)
|
|
150
|
+
- Sync fork with upstream: `repo` (you must have push rights to your fork)
|
|
151
|
+
- Delete repository: `delete_repo` (and admin rights on the repo)
|
|
152
|
+
|
|
153
|
+
Notes:
|
|
154
|
+
- Organization repositories may require that your token is SSO-authorized if the org enforces SSO.
|
|
155
|
+
- If organization data doesn’t appear in the switcher, ensure your token is authorized for that org and consider adding `read:org` (some org setups require it to list memberships).
|
|
156
|
+
- Fine-grained PATs: grant Repository access to the repos you need and enable at least:
|
|
157
|
+
- Metadata: Read
|
|
158
|
+
- Contents: Read (list/search), Read & Write (sync/archive)
|
|
159
|
+
- Administration: Manage (only if you need delete)
|
|
160
|
+
If in doubt, the classic `repo` scope plus `delete_repo` (for deletion) is the simplest equivalent.
|
|
161
|
+
|
|
142
162
|
## Usage
|
|
143
163
|
|
|
144
164
|
Launch the app, then use the keys below:
|
|
145
165
|
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
166
|
+
### Navigation & View Controls
|
|
167
|
+
- **Top/Bottom**: `Ctrl+G` (top), `G` (bottom)
|
|
168
|
+
- **Page Navigation**: ↑↓ Arrow keys, PageUp/PageDown
|
|
169
|
+
- **Search**: `/` to enter search mode, type 3+ characters for server-side search
|
|
149
170
|
- Down arrow or Enter: Start browsing search results
|
|
150
171
|
- Esc: Clear search and return to full repository list
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
- **Sort**: `S` opens sort modal with options:
|
|
173
|
+
- Updated: When the repository was last modified
|
|
174
|
+
- Pushed: When code was last pushed
|
|
175
|
+
- Name: Alphabetical by repository name
|
|
176
|
+
- Stars: Number of stars
|
|
177
|
+
- **Sort Direction**: `D` to toggle ascending/descending
|
|
178
|
+
- **Display Density**: `T` to toggle compact/cozy/comfy
|
|
179
|
+
- **Fork Status**: `F` to toggle showing commits behind upstream
|
|
180
|
+
- **Visibility Filter**: `V` opens modal (All, Public, Private, Internal for enterprise)
|
|
181
|
+
|
|
182
|
+
### Navigation & Account
|
|
183
|
+
- **Open in browser**: Enter or `O`
|
|
184
|
+
- **Refresh**: `R`
|
|
185
|
+
- **Organization switcher**: `W` to switch between personal account and organizations
|
|
186
|
+
- **Logout**: `Ctrl+L`
|
|
187
|
+
- **Quit**: `Q`
|
|
188
|
+
|
|
189
|
+
### Repository Actions
|
|
190
|
+
- **Repository info**: `I` to view detailed metadata (size, language, timestamps)
|
|
191
|
+
- **Cache info**: `Ctrl+I` to inspect Apollo cache status
|
|
192
|
+
- **Archive/Unarchive**: `Ctrl+A` with confirmation prompt
|
|
193
|
+
- **Delete repository**: `Del` or `Backspace` (with two-step confirmation modal)
|
|
194
|
+
- Type confirmation code → confirm (Y/Enter)
|
|
160
195
|
- Cancel: press `C` or Esc
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
196
|
+
- **Sync fork**: `Ctrl+S` (for forks only, shows commit status and handles conflicts)
|
|
197
|
+
|
|
198
|
+
### General
|
|
199
|
+
- **Esc**: Cancels modals, clears search, or returns to normal listing (does not quit)
|
|
200
|
+
|
|
201
|
+
The header displays the current owner context (Personal Account or Organization name), active sort and direction, fork status tracking state, and active search/filter.
|
|
167
202
|
|
|
168
203
|
Status bar shows loaded count vs total. A rate-limit line displays `remaining/limit` and the reset time; it turns yellow when remaining ≤ 10% of the limit.
|
|
169
204
|
|
|
@@ -284,6 +319,8 @@ Recently implemented:
|
|
|
284
319
|
- ✅ Organization support and switching (press `W`)
|
|
285
320
|
- ✅ Enhanced server-side search with improved UX
|
|
286
321
|
- ✅ Smart infinite scroll with 80% prefetch trigger
|
|
322
|
+
- ✅ Modal-based sort and visibility filtering
|
|
323
|
+
- ✅ Server-side visibility filtering for accurate pagination
|
|
287
324
|
|
|
288
325
|
Highlights on deck:
|
|
289
326
|
- Optional OS keychain storage via `keytar`
|
|
@@ -118,7 +118,7 @@ async function fetchViewerOrganizations(client) {
|
|
|
118
118
|
const res = await client(query);
|
|
119
119
|
return res.viewer.organizations.nodes;
|
|
120
120
|
}
|
|
121
|
-
async function fetchViewerReposPage(client, first, after, orderBy, includeForkTracking = true, ownerAffiliations = ["OWNER"], organizationLogin) {
|
|
121
|
+
async function fetchViewerReposPage(client, first, after, orderBy, includeForkTracking = true, ownerAffiliations = ["OWNER"], organizationLogin, privacy) {
|
|
122
122
|
const sortField = orderBy?.field || "UPDATED_AT";
|
|
123
123
|
const sortDirection = orderBy?.direction || "DESC";
|
|
124
124
|
const isOrgContext = !!organizationLogin;
|
|
@@ -132,6 +132,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
132
132
|
$sortField: RepositoryOrderField!
|
|
133
133
|
$sortDirection: OrderDirection!
|
|
134
134
|
$orgLogin: String!
|
|
135
|
+
$privacy: RepositoryPrivacy
|
|
135
136
|
) {
|
|
136
137
|
rateLimit {
|
|
137
138
|
limit
|
|
@@ -143,6 +144,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
143
144
|
first: $first
|
|
144
145
|
after: $after
|
|
145
146
|
orderBy: { field: $sortField, direction: $sortDirection }
|
|
147
|
+
privacy: $privacy
|
|
146
148
|
) {
|
|
147
149
|
totalCount
|
|
148
150
|
pageInfo {
|
|
@@ -207,7 +209,8 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
207
209
|
after: after ?? null,
|
|
208
210
|
sortField,
|
|
209
211
|
sortDirection,
|
|
210
|
-
orgLogin: organizationLogin
|
|
212
|
+
orgLogin: organizationLogin,
|
|
213
|
+
privacy: privacy ?? null
|
|
211
214
|
});
|
|
212
215
|
const data2 = res2.organization.repositories;
|
|
213
216
|
return {
|
|
@@ -227,6 +230,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
227
230
|
$sortField: RepositoryOrderField!
|
|
228
231
|
$sortDirection: OrderDirection!
|
|
229
232
|
$affiliations: [RepositoryAffiliation!]!
|
|
233
|
+
$privacy: RepositoryPrivacy
|
|
230
234
|
) {
|
|
231
235
|
rateLimit {
|
|
232
236
|
limit
|
|
@@ -239,6 +243,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
239
243
|
first: $first
|
|
240
244
|
after: $after
|
|
241
245
|
orderBy: { field: $sortField, direction: $sortDirection }
|
|
246
|
+
privacy: $privacy
|
|
242
247
|
) {
|
|
243
248
|
totalCount
|
|
244
249
|
pageInfo {
|
|
@@ -303,7 +308,8 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
303
308
|
after: after ?? null,
|
|
304
309
|
sortField,
|
|
305
310
|
sortDirection,
|
|
306
|
-
affiliations: ownerAffiliations
|
|
311
|
+
affiliations: ownerAffiliations,
|
|
312
|
+
privacy: privacy ?? null
|
|
307
313
|
});
|
|
308
314
|
const data = res.viewer.repositories;
|
|
309
315
|
return {
|
|
@@ -314,7 +320,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
314
320
|
rateLimit: res.rateLimit
|
|
315
321
|
};
|
|
316
322
|
}
|
|
317
|
-
async function fetchViewerReposPageUnified(token, first, after, orderBy, includeForkTracking = true, fetchPolicy = "cache-first", ownerAffiliations = ["OWNER"], organizationLogin) {
|
|
323
|
+
async function fetchViewerReposPageUnified(token, first, after, orderBy, includeForkTracking = true, fetchPolicy = "cache-first", ownerAffiliations = ["OWNER"], organizationLogin, privacy) {
|
|
318
324
|
const isApolloEnabled = true;
|
|
319
325
|
const debug = process.env.GH_MANAGER_DEBUG === "1";
|
|
320
326
|
const isOrgContext = !!organizationLogin;
|
|
@@ -328,14 +334,14 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
328
334
|
const sortField = orderBy?.field || "UPDATED_AT";
|
|
329
335
|
const sortDirection = orderBy?.direction || "DESC";
|
|
330
336
|
let q;
|
|
331
|
-
let variables = { first, after: after ?? null, sortField, sortDirection };
|
|
337
|
+
let variables = { first, after: after ?? null, sortField, sortDirection, privacy: privacy ?? null };
|
|
332
338
|
if (isOrgContext) {
|
|
333
339
|
variables.orgLogin = organizationLogin;
|
|
334
340
|
q = ap.gql`
|
|
335
|
-
query OrgRepos($first: Int!, $after: String, $sortField: RepositoryOrderField!, $sortDirection: OrderDirection!, $orgLogin: String
|
|
341
|
+
query OrgRepos($first: Int!, $after: String, $sortField: RepositoryOrderField!, $sortDirection: OrderDirection!, $orgLogin: String!, $privacy: RepositoryPrivacy) {
|
|
336
342
|
rateLimit { limit remaining resetAt }
|
|
337
343
|
organization(login: $orgLogin) {
|
|
338
|
-
repositories(first: $first, after: $after, orderBy: { field: $sortField, direction: $sortDirection }) {
|
|
344
|
+
repositories(first: $first, after: $after, orderBy: { field: $sortField, direction: $sortDirection }, privacy: $privacy) {
|
|
339
345
|
totalCount
|
|
340
346
|
pageInfo { endCursor hasNextPage }
|
|
341
347
|
nodes {
|
|
@@ -366,10 +372,10 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
366
372
|
} else {
|
|
367
373
|
variables.affiliations = ownerAffiliations;
|
|
368
374
|
q = ap.gql`
|
|
369
|
-
query ViewerRepos($first: Int!, $after: String, $sortField: RepositoryOrderField!, $sortDirection: OrderDirection!, $affiliations: [RepositoryAffiliation!]
|
|
375
|
+
query ViewerRepos($first: Int!, $after: String, $sortField: RepositoryOrderField!, $sortDirection: OrderDirection!, $affiliations: [RepositoryAffiliation!]!, $privacy: RepositoryPrivacy) {
|
|
370
376
|
rateLimit { limit remaining resetAt }
|
|
371
377
|
viewer {
|
|
372
|
-
repositories(ownerAffiliations: $affiliations, first: $first, after: $after, orderBy: { field: $sortField, direction: $sortDirection }) {
|
|
378
|
+
repositories(ownerAffiliations: $affiliations, first: $first, after: $after, orderBy: { field: $sortField, direction: $sortDirection }, privacy: $privacy) {
|
|
373
379
|
totalCount
|
|
374
380
|
pageInfo { endCursor hasNextPage }
|
|
375
381
|
nodes {
|
|
@@ -424,7 +430,7 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
424
430
|
}
|
|
425
431
|
if (debug) console.log("\u{1F4E1} Using Octokit fallback...");
|
|
426
432
|
const octo = makeClient(token);
|
|
427
|
-
return fetchViewerReposPage(octo, first, after, orderBy, includeForkTracking, ownerAffiliations, organizationLogin);
|
|
433
|
+
return fetchViewerReposPage(octo, first, after, orderBy, includeForkTracking, ownerAffiliations, organizationLogin, privacy);
|
|
428
434
|
}
|
|
429
435
|
async function searchRepositoriesUnified(token, viewer, text, first, after, sortKey = "UPDATED_AT", sortDir = "DESC", includeForkTracking = true, fetchPolicy = "network-only") {
|
|
430
436
|
const q = `${text} user:${viewer} in:name,description fork:true`;
|