gh-manager-cli 1.10.6 → 1.11.1

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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.11.1](https://github.com/wiiiimm/gh-manager-cli/compare/v1.11.0...v1.11.1) (2025-09-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * handle require not defined error in GitHub Actions script ([49db95b](https://github.com/wiiiimm/gh-manager-cli/commit/49db95b8f69acbeb075190d81d7d5dcd6dc2991e))
7
+
8
+ # [1.11.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.10.6...v1.11.0) (2025-09-01)
9
+
10
+
11
+ ### Features
12
+
13
+ * add modal-based visibility filtering and sort selection ([1c72224](https://github.com/wiiiimm/gh-manager-cli/commit/1c72224b4854f69481bb21f089a926dc2c18e53f))
14
+
1
15
  ## [1.10.6](https://github.com/wiiiimm/gh-manager-cli/compare/v1.10.5...v1.10.6) (2025-09-01)
2
16
 
3
17
 
package/README.md CHANGED
@@ -10,25 +10,28 @@
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
- ## Screenshots
14
-
15
- <div align="center">
16
-
17
- ### Repository Listing
18
- Browse your repositories with rich metadata, sorting, and filtering options.
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>
19
18
 
20
- <img src="docs/demo_repo_listing.png" alt="Repository listing interface showing repositories with metadata" width="800">
19
+ ## Documentation
21
20
 
22
- ### Authentication Flow
23
- Secure GitHub token authentication with validation and persistence.
21
+ | Getting Started | Features | Development |
22
+ |-----------------|----------|-------------|
23
+ | [📥 Installation](wiki/Installation.md) | [🔍 Features Overview](wiki/Features.md) | [🛠️ Development Guide](wiki/Development.md) |
24
+ | [🔑 Token & Security](wiki/Token-and-Security.md) | [⌨️ Usage & Controls](wiki/Usage.md) | [🧪 Testing](wiki/Testing.md) |
25
+ | [❓ Troubleshooting](wiki/Troubleshooting.md) | [🗺️ Roadmap](wiki/Roadmap.md) | [🏠 Wiki Home](wiki/README.md) |
24
26
 
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">
27
+ ## Screenshots
31
28
 
29
+ <div align="center">
30
+ <img src="docs/demo_repo_listing.png" alt="Repository listing with metadata" width="31%" />
31
+ <img src="docs/demo_login.png" alt="GitHub token authentication flow" width="31%" />
32
+ <img src="docs/demo_delete_confirmation.png" alt="Two-step delete confirmation" width="31%" />
33
+ <br />
34
+ <sub>Listing • Auth • Delete confirmation</sub>
32
35
  </div>
33
36
 
34
37
  ## Quick Start
@@ -46,28 +49,34 @@ On first run, you'll be prompted for a GitHub Personal Access Token.
46
49
  - **Token Authentication**: Secure PAT storage with validation and persistence
47
50
  - **Repository Listing**: Browse all your personal repositories with metadata (stars, forks, language, etc.)
48
51
  - **Live Pagination**: Infinite scroll with automatic page prefetching
49
- - **Real-time Sorting**: Server-side sorting by updated, pushed, name, or stars (with direction toggle)
52
+ - **Interactive Sorting**: Modal-based sort selection (updated, pushed, name, stars) with direction toggle
50
53
  - **Smart Search**: Server-side search through repository names and descriptions (3+ characters)
54
+ - **Visibility Filtering**: Modal-based visibility filter (All, Public, Private, Internal for enterprise) with server-side filtering
55
+ - **Fork Status Tracking**: Toggle display of commits behind upstream for forked repositories
51
56
  - **Repository Actions**:
52
57
  - View detailed info (`I`) - Shows repository metadata, language, size, and timestamps
53
58
  - Open in browser (Enter/`O`)
54
- - Delete repository (`Del` or `Ctrl+Backspace`) with secure two-step confirmation
59
+ - Delete repository (`Del` or `Backspace`) with secure two-step confirmation
55
60
  - Archive/unarchive repositories (`Ctrl+A`) with confirmation prompts
56
61
  - Sync forks with upstream (`Ctrl+S`) with automatic conflict detection
57
62
 
58
63
  ### User Interface & Experience
59
64
  - **Keyboard Navigation**: Full keyboard control (arrow keys, PageUp/Down, `Ctrl+G`/`G`)
60
65
  - **Display Density**: Toggle between compact/cozy/comfy spacing (`T`)
61
- - **Visual Indicators**: Fork status, private/archived badges, language colors
66
+ - **Visual Indicators**: Fork status, private/archived badges, language colors, visibility status
67
+ - **Interactive Modals**: Sort selection, visibility filtering, and organization switching via modal dialogs
68
+ - **Balanced Layout**: Repository items with spacing above and below for better visual hierarchy
62
69
  - **Loading States**: Contextual loading screens for sorting and refreshing operations
63
70
  - **Rate Limit Monitoring**: Live API usage display with visual warnings
71
+ - **Improved Layout**: Balanced spacing above and below repository items for better visual hierarchy
64
72
 
65
73
  ### Technical Features
66
- - **Preference Persistence**: UI settings (sort, density) saved between sessions
74
+ - **Preference Persistence**: UI settings (sort, density, visibility filter, fork tracking) saved between sessions
75
+ - **Server-side Filtering**: Visibility filtering performed at GitHub API level for accurate pagination
67
76
  - **Cross-platform**: Works on macOS, Linux, and Windows
68
77
  - **Secure Storage**: Token stored with proper file permissions (0600)
69
78
  - **Error Handling**: Graceful error recovery with retry mechanisms
70
- - **Performance**: Efficient GraphQL queries with virtualized rendering
79
+ - **Performance**: Efficient GraphQL queries with virtualized rendering and server-side filtering
71
80
 
72
81
  ## Installation
73
82
 
@@ -139,31 +148,65 @@ The app needs a GitHub token to read your repositories.
139
148
 
140
149
  Note: Tokens are stored in plaintext on disk with restricted permissions. Future work may add OS keychain support.
141
150
 
151
+ ### PAT Permissions & Scopes
152
+
153
+ Choose the least-privileged token for the features you plan to use:
154
+
155
+ - Browsing/searching repos (public only): `public_repo`
156
+ - Browsing/searching repos (includes private): `repo`
157
+ - Archive/Unarchive repository: `repo` (and you must have admin or maintainer rights on the repo)
158
+ - Sync fork with upstream: `repo` (you must have push rights to your fork)
159
+ - Delete repository: `delete_repo` (and admin rights on the repo)
160
+
161
+ Notes:
162
+ - Organization repositories may require that your token is SSO-authorized if the org enforces SSO.
163
+ - 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).
164
+ - Fine-grained PATs: grant Repository access to the repos you need and enable at least:
165
+ - Metadata: Read
166
+ - Contents: Read (list/search), Read & Write (sync/archive)
167
+ - Administration: Manage (only if you need delete)
168
+ If in doubt, the classic `repo` scope plus `delete_repo` (for deletion) is the simplest equivalent.
169
+
142
170
  ## Usage
143
171
 
144
172
  Launch the app, then use the keys below:
145
173
 
146
- - Navigation: Up/Down, PageUp/PageDown, `Ctrl+G` (top), `G` (bottom)
147
- - Refresh: `R`
148
- - Search: `/` to enter search mode, type 3+ characters for server-side search
174
+ ### Navigation & View Controls
175
+ - **Top/Bottom**: `Ctrl+G` (top), `G` (bottom)
176
+ - **Page Navigation**: ↑↓ Arrow keys, PageUp/PageDown
177
+ - **Search**: `/` to enter search mode, type 3+ characters for server-side search
149
178
  - Down arrow or Enter: Start browsing search results
150
179
  - Esc: Clear search and return to full repository list
151
- - Sorting: `S` to cycle field (updated → pushed → name → stars → forks), `D` to toggle direction
152
- - Display density: `T` to toggle compact/cozy/comfy
153
- - Workspace switcher: `W` to switch between personal account and organizations
154
- - Repository info: `I` to view detailed metadata (size, language, timestamps)
155
- - Open in browser: Enter or `O`
156
- - Delete repository: `Del` or `Ctrl+Backspace` (with confirmation modal)
157
- - Uses GitHub REST API (requires `delete_repo` scope and admin rights)
158
- - Two-step confirm: type code confirm (Y/Enter)
159
- - Confirm: press `Y` or Enter
180
+ - **Sort**: `S` opens sort modal with options:
181
+ - Updated: When the repository was last modified
182
+ - Pushed: When code was last pushed
183
+ - Name: Alphabetical by repository name
184
+ - Stars: Number of stars
185
+ - **Sort Direction**: `D` to toggle ascending/descending
186
+ - **Display Density**: `T` to toggle compact/cozy/comfy
187
+ - **Fork Status**: `F` to toggle showing commits behind upstream
188
+ - **Visibility Filter**: `V` opens modal (All, Public, Private, Internal for enterprise)
189
+
190
+ ### Navigation & Account
191
+ - **Open in browser**: Enter or `O`
192
+ - **Refresh**: `R`
193
+ - **Organization switcher**: `W` to switch between personal account and organizations
194
+ - **Logout**: `Ctrl+L`
195
+ - **Quit**: `Q`
196
+
197
+ ### Repository Actions
198
+ - **Repository info**: `I` to view detailed metadata (size, language, timestamps)
199
+ - **Cache info**: `Ctrl+I` to inspect Apollo cache status
200
+ - **Archive/Unarchive**: `Ctrl+A` with confirmation prompt
201
+ - **Delete repository**: `Del` or `Backspace` (with two-step confirmation modal)
202
+ - Type confirmation code → confirm (Y/Enter)
160
203
  - Cancel: press `C` or Esc
161
- - Archive/Unarchive: `Ctrl+A`
162
- - Sync fork with upstream: `Ctrl+S` (for forks only, shows commit status and handles conflicts)
163
- - Logout: `Ctrl+L`
164
- - Toggle fork metrics: `F`
165
- - Quit: `Q`
166
- - Esc: cancels modals, clears search, or returns to normal listing (does not quit)
204
+ - **Sync fork**: `Ctrl+S` (for forks only, shows commit status and handles conflicts)
205
+
206
+ ### General
207
+ - **Esc**: Cancels modals, clears search, or returns to normal listing (does not quit)
208
+
209
+ 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
210
 
168
211
  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
212
 
@@ -284,6 +327,8 @@ Recently implemented:
284
327
  - ✅ Organization support and switching (press `W`)
285
328
  - ✅ Enhanced server-side search with improved UX
286
329
  - ✅ Smart infinite scroll with 80% prefetch trigger
330
+ - ✅ Modal-based sort and visibility filtering
331
+ - ✅ Server-side visibility filtering for accurate pagination
287
332
 
288
333
  Highlights on deck:
289
334
  - 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`;
@@ -17,7 +17,7 @@ import {
17
17
  updateCacheAfterArchive,
18
18
  updateCacheAfterDelete,
19
19
  updateCacheWithRepository
20
- } from "./chunk-XCFI3TG5.js";
20
+ } from "./chunk-75Y3BQBE.js";
21
21
  export {
22
22
  archiveRepositoryById,
23
23
  deleteRepositoryRest,