gh-manager-cli 1.13.2 → 1.15.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 CHANGED
@@ -1,3 +1,27 @@
1
+ # [1.15.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.14.0...v1.15.0) (2025-09-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * implement GitHub Device Authorization Grant flow ([#13](https://github.com/wiiiimm/gh-manager-cli/issues/13)) ([d259534](https://github.com/wiiiimm/gh-manager-cli/commit/d259534a8a0e5c21270da17df4256aec6b0b216c))
7
+
8
+ # [1.14.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.13.2...v1.14.0) (2025-09-02)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * correct TextInput import from ink-text-input package ([1040eca](https://github.com/wiiiimm/gh-manager-cli/commit/1040ecac07b0976eaf916ee5593d0bc867590eda))
14
+ * update both visibility and isPrivate fields when changing visibility ([4b96234](https://github.com/wiiiimm/gh-manager-cli/commit/4b962345a1e490b3a675575c8726e7040e0c0ed3))
15
+ * use REST API for changing repository visibility ([0bd1475](https://github.com/wiiiimm/gh-manager-cli/commit/0bd1475f22d5802d919491ad507d4b38f158da5e))
16
+
17
+
18
+ ### Features
19
+
20
+ * add enterprise support with Internal visibility and Ctrl+V visibility change ([9f457af](https://github.com/wiiiimm/gh-manager-cli/commit/9f457af4558458a23f321c14b42f0701fefecc0f))
21
+ * add repository visibility change with Ctrl+V ([d51b5e8](https://github.com/wiiiimm/gh-manager-cli/commit/d51b5e8b28c78f6ba58056a3254fc1aa218d9838))
22
+ * add support for Internal visibility in enterprise accounts ([781575a](https://github.com/wiiiimm/gh-manager-cli/commit/781575aee55a70f869aad7d724fd50b6bd052221))
23
+ * respect visibility filter when changing repository visibility ([25c963c](https://github.com/wiiiimm/gh-manager-cli/commit/25c963ca89038c84e0c8711f69d71785eb3a7355))
24
+
1
25
  ## [1.13.2](https://github.com/wiiiimm/gh-manager-cli/compare/v1.13.1...v1.13.2) (2025-09-01)
2
26
 
3
27
 
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="docs/assets/logo-horizontal.png" alt="gh-manager-cli logo" width="400" />
3
+ </p>
4
+
1
5
  # gh-manager-cli
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/gh-manager-cli.svg)](https://www.npmjs.com/package/gh-manager-cli)
@@ -41,34 +45,36 @@ Interactive terminal app to browse and manage your personal GitHub repositories.
41
45
  npx gh-manager-cli
42
46
  ```
43
47
 
44
- On first run, you'll be prompted for a GitHub Personal Access Token.
48
+ On first run, you'll be prompted to authenticate with GitHub (OAuth recommended).
45
49
 
46
50
  ## Features
47
51
 
48
52
  ### Core Repository Management
49
- - **Token Authentication**: Secure PAT storage with validation and persistence
53
+ - **Authentication**: GitHub OAuth (recommended) or Personal Access Token with secure storage
50
54
  - **Repository Listing**: Browse all your personal repositories with metadata (stars, forks, language, etc.)
51
55
  - **Live Pagination**: Infinite scroll with automatic page prefetching
52
56
  - **Interactive Sorting**: Modal-based sort selection (updated, pushed, name, stars) with direction toggle
53
57
  - **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
58
+ - **Visibility Filtering**: Modal-based visibility filter (All, Public, Private/Internal for enterprise) with smart filtering
55
59
  - **Fork Status Tracking**: Toggle display of commits behind upstream for forked repositories
56
60
  - **Repository Actions**:
57
61
  - View detailed info (`I`) - Shows repository metadata, language, size, and timestamps
58
62
  - Open in browser (Enter/`O`)
59
63
  - Delete repository (`Del` or `Backspace`) with secure two-step confirmation
60
64
  - Archive/unarchive repositories (`Ctrl+A`) with confirmation prompts
65
+ - Change repository visibility (`Ctrl+V`) - Switch between Public, Private, and Internal (enterprise only)
61
66
  - Sync forks with upstream (`Ctrl+S`) with automatic conflict detection
62
67
 
63
68
  ### User Interface & Experience
64
69
  - **Keyboard Navigation**: Full keyboard control (arrow keys, PageUp/Down, `Ctrl+G`/`G`)
65
70
  - **Display Density**: Toggle between compact/cozy/comfy spacing (`T`)
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
71
+ - **Visual Indicators**: Fork status, private/internal/archived badges, language colors, visibility status
72
+ - **Enterprise Support**: Full support for GitHub Enterprise with Internal repository visibility
73
+ - **Organization Context**: Switch between personal and organization accounts with ENT badge for enterprise orgs
74
+ - **Interactive Modals**: Sort selection, visibility filtering, organization switching, and visibility change dialogs
68
75
  - **Balanced Layout**: Repository items with spacing above and below for better visual hierarchy
69
76
  - **Loading States**: Contextual loading screens for sorting and refreshing operations
70
77
  - **Rate Limit Monitoring**: Live API usage display with visual warnings
71
- - **Improved Layout**: Balanced spacing above and below repository items for better visual hierarchy
72
78
 
73
79
  ### Technical Features
74
80
  - **Preference Persistence**: UI settings (sort, density, visibility filter, fork tracking) saved between sessions
@@ -140,18 +146,41 @@ pnpm link
140
146
  gh-manager-cli
141
147
  ```
142
148
 
143
- ## Token & Security
149
+ ## Authentication
150
+
151
+ The app supports two authentication methods:
152
+
153
+ ### 1. GitHub OAuth (Recommended) 🎯
154
+
155
+ The easiest and most secure way to authenticate:
144
156
 
145
- The app needs a GitHub token to read your repositories.
157
+ - **Device Flow**: No need to handle callback URLs - just enter a code on GitHub's website
158
+ - **Browser-based**: Opens GitHub's authorization page automatically
159
+ - **Secure**: No client secrets or sensitive data in the app
160
+ - **Full Permissions**: Automatically requests all necessary scopes for complete functionality
161
+ - **User-friendly**: No manual token management required
162
+
163
+ When you first run the app, select **"GitHub OAuth (Recommended)"** from the authentication options. The app will:
164
+ 1. Display a device code for you to enter on GitHub
165
+ 2. Open your browser to GitHub's device authorization page
166
+ 3. Wait for you to authorize the app
167
+ 4. Securely store the OAuth token for future use
168
+
169
+ ### 2. Personal Access Token (PAT)
170
+
171
+ Alternative method for users who prefer manual token management:
146
172
 
147
173
  - Provide via env var: `GITHUB_TOKEN` or `GH_TOKEN`, or enter when prompted on first run.
148
- - Recommended: classic PAT with `repo` scope for listing both public and private repos (read is sufficient).
174
+ - Recommended: classic PAT with `repo` scope for listing both public and private repos.
149
175
  - Validation: a minimal `viewer { login }` request verifies the token.
150
- - Storage: token is saved as JSON in your OS user config directory with POSIX perms `0600`.
176
+
177
+ ### Token Storage & Security
178
+
179
+ - Storage: tokens are saved as JSON in your OS user config directory with POSIX perms `0600`.
151
180
  - macOS: `~/Library/Preferences/gh-manager-cli/config.json`
152
181
  - Linux: `~/.config/gh-manager-cli/config.json`
153
182
  - Windows: `%APPDATA%\gh-manager-cli\config.json`
154
- - Revocation: you can revoke the PAT at any time in your GitHub settings.
183
+ - Revocation: you can revoke tokens at any time in your GitHub settings.
155
184
 
156
185
  Note: Tokens are stored in plaintext on disk with restricted permissions. Future work may add OS keychain support.
157
186
 
@@ -192,7 +221,7 @@ Launch the app, then use the keys below:
192
221
  - **Sort Direction**: `D` to toggle ascending/descending
193
222
  - **Display Density**: `T` to toggle compact/cozy/comfy
194
223
  - **Fork Status**: `F` to toggle showing commits behind upstream
195
- - **Visibility Filter**: `V` opens modal (All, Public, Private, Internal for enterprise)
224
+ - **Visibility Filter**: `V` opens modal (All, Public, Private/Internal for enterprise)
196
225
 
197
226
  ### Navigation & Account
198
227
  - **Open in browser**: Enter or `O`
@@ -205,6 +234,7 @@ Launch the app, then use the keys below:
205
234
  - **Repository info**: `I` to view detailed metadata (size, language, timestamps)
206
235
  - **Cache info**: `K` to inspect Apollo cache status
207
236
  - **Archive/Unarchive**: `Ctrl+A` with confirmation prompt
237
+ - **Change visibility**: `Ctrl+V` to change repository visibility (Public/Private/Internal)
208
238
  - **Delete repository**: `Del` or `Backspace` (with two-step confirmation modal)
209
239
  - Type confirmation code → confirm (Y/Enter)
210
240
  - Cancel: press `C` or Esc
@@ -329,13 +359,16 @@ REPOS_PER_FETCH=5 GH_MANAGER_DEBUG=1 npx gh-manager-cli-cli
329
359
  For the up-to-date task board, see [TODOs.md](./TODOs.md).
330
360
 
331
361
  Recently implemented:
362
+ - ✅ OAuth login flow as an alternative to Personal Access Token
332
363
  - ✅ Density toggle for row spacing (compact/cozy/comfy)
333
- - ✅ Repo actions (archive/unarchive, delete) with confirmations
334
- - ✅ Organization support and switching (press `W`)
335
- - ✅ Enhanced server-side search with improved UX
364
+ - ✅ Repo actions (archive/unarchive, delete, change visibility) with confirmations
365
+ - ✅ Organization support and switching (press `W`) with enterprise detection
366
+ - ✅ Enhanced server-side search with improved UX and organization context support
336
367
  - ✅ Smart infinite scroll with 80% prefetch trigger
337
368
  - ✅ Modal-based sort and visibility filtering
338
- - ✅ Server-side visibility filtering for accurate pagination
369
+ - ✅ GitHub Enterprise support with Internal repository visibility
370
+ - ✅ Change repository visibility modal (`Ctrl+V`)
371
+ - ✅ Compact filter modals for better screen space utilization
339
372
 
340
373
  Highlights on deck:
341
374
  - Optional OS keychain storage via `keytar`
@@ -139,6 +139,26 @@ async function fetchViewerOrganizations(client) {
139
139
  const res = await client(query);
140
140
  return res.viewer.organizations.nodes;
141
141
  }
142
+ async function checkOrganizationIsEnterprise(client, orgLogin) {
143
+ try {
144
+ const query = (
145
+ /* GraphQL */
146
+ `
147
+ query CheckOrgEnterprise($orgLogin: String!) {
148
+ organization(login: $orgLogin) {
149
+ enterpriseOwners(first: 1) {
150
+ totalCount
151
+ }
152
+ }
153
+ }
154
+ `
155
+ );
156
+ const res = await client(query, { orgLogin });
157
+ return res.organization?.enterpriseOwners?.totalCount > 0;
158
+ } catch (error) {
159
+ return false;
160
+ }
161
+ }
142
162
  async function fetchViewerReposPage(client, first, after, orderBy, includeForkTracking = true, ownerAffiliations = ["OWNER"], organizationLogin, privacy) {
143
163
  const sortField = orderBy?.field || "UPDATED_AT";
144
164
  const sortDirection = orderBy?.direction || "DESC";
@@ -453,8 +473,9 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
453
473
  const octo = makeClient(token);
454
474
  return fetchViewerReposPage(octo, first, after, orderBy, includeForkTracking, ownerAffiliations, organizationLogin, privacy);
455
475
  }
456
- async function searchRepositoriesUnified(token, viewer, text, first, after, sortKey = "UPDATED_AT", sortDir = "DESC", includeForkTracking = true, fetchPolicy = "network-only") {
457
- const q = `${text} user:${viewer} in:name,description fork:true`;
476
+ async function searchRepositoriesUnified(token, viewer, text, first, after, sortKey = "UPDATED_AT", sortDir = "DESC", includeForkTracking = true, fetchPolicy = "network-only", organizationLogin) {
477
+ const searchContext = organizationLogin ? `org:${organizationLogin}` : `user:${viewer}`;
478
+ const q = `${text} ${searchContext} in:name,description fork:true`;
458
479
  try {
459
480
  const ap = await makeApolloClient(token);
460
481
  const queryDoc = ap.gql`
@@ -565,6 +586,48 @@ async function unarchiveRepositoryById(client, repositoryId) {
565
586
  );
566
587
  await client(mutation, { repositoryId });
567
588
  }
589
+ async function changeRepositoryVisibility(client, repositoryId, visibility, token) {
590
+ const query = (
591
+ /* GraphQL */
592
+ `
593
+ query GetRepoDetails($id: ID!) {
594
+ node(id: $id) {
595
+ ... on Repository {
596
+ nameWithOwner
597
+ owner {
598
+ login
599
+ }
600
+ name
601
+ }
602
+ }
603
+ }
604
+ `
605
+ );
606
+ const result = await client(query, { id: repositoryId });
607
+ const repo = result.node;
608
+ if (!repo || !repo.nameWithOwner) {
609
+ throw new Error("Repository not found");
610
+ }
611
+ const [owner, name] = repo.nameWithOwner.split("/");
612
+ const response = await fetch(`https://api.github.com/repos/${owner}/${name}`, {
613
+ method: "PATCH",
614
+ headers: {
615
+ "Authorization": `token ${token}`,
616
+ "Accept": "application/vnd.github+json",
617
+ "Content-Type": "application/json",
618
+ "User-Agent": "gh-manager-cli"
619
+ },
620
+ body: JSON.stringify({
621
+ visibility: visibility.toLowerCase()
622
+ // API expects lowercase
623
+ })
624
+ });
625
+ if (!response.ok) {
626
+ const error = await response.text();
627
+ throw new Error(`Failed to change visibility: ${error}`);
628
+ }
629
+ return { nameWithOwner: repo.nameWithOwner };
630
+ }
568
631
  async function getRepositoryFromCache(token, repositoryId) {
569
632
  try {
570
633
  const ap = await makeApolloClient(token);
@@ -755,6 +818,21 @@ async function updateCacheAfterArchive(token, repositoryId, isArchived) {
755
818
  } catch {
756
819
  }
757
820
  }
821
+ async function updateCacheAfterVisibilityChange(token, repositoryId, visibility) {
822
+ try {
823
+ const ap = await makeApolloClient(token);
824
+ if (!ap || !ap.client) return;
825
+ const isPrivate = visibility === "PRIVATE";
826
+ ap.client.cache.modify({
827
+ id: `Repository:${repositoryId}`,
828
+ fields: {
829
+ visibility: () => visibility,
830
+ isPrivate: () => isPrivate
831
+ }
832
+ });
833
+ } catch {
834
+ }
835
+ }
758
836
  async function updateCacheWithRepository(token, repository) {
759
837
  try {
760
838
  const ap = await makeApolloClient(token);
@@ -860,18 +938,21 @@ export {
860
938
  makeClient,
861
939
  getViewerLogin,
862
940
  fetchViewerOrganizations,
941
+ checkOrganizationIsEnterprise,
863
942
  fetchViewerReposPage,
864
943
  fetchViewerReposPageUnified,
865
944
  searchRepositoriesUnified,
866
945
  deleteRepositoryRest,
867
946
  archiveRepositoryById,
868
947
  unarchiveRepositoryById,
948
+ changeRepositoryVisibility,
869
949
  getRepositoryFromCache,
870
950
  fetchRepositoryById,
871
951
  syncForkWithUpstream,
872
952
  purgeApolloCacheFiles,
873
953
  updateCacheAfterDelete,
874
954
  updateCacheAfterArchive,
955
+ updateCacheAfterVisibilityChange,
875
956
  updateCacheWithRepository,
876
957
  inspectCacheStatus
877
958
  };
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  archiveRepositoryById,
4
+ changeRepositoryVisibility,
5
+ checkOrganizationIsEnterprise,
4
6
  deleteRepositoryRest,
5
7
  fetchRepositoryById,
6
8
  fetchViewerOrganizations,
@@ -16,10 +18,13 @@ import {
16
18
  unarchiveRepositoryById,
17
19
  updateCacheAfterArchive,
18
20
  updateCacheAfterDelete,
21
+ updateCacheAfterVisibilityChange,
19
22
  updateCacheWithRepository
20
- } from "./chunk-BOS4OCY4.js";
23
+ } from "./chunk-OKP742N4.js";
21
24
  export {
22
25
  archiveRepositoryById,
26
+ changeRepositoryVisibility,
27
+ checkOrganizationIsEnterprise,
23
28
  deleteRepositoryRest,
24
29
  fetchRepositoryById,
25
30
  fetchViewerOrganizations,
@@ -35,5 +40,6 @@ export {
35
40
  unarchiveRepositoryById,
36
41
  updateCacheAfterArchive,
37
42
  updateCacheAfterDelete,
43
+ updateCacheAfterVisibilityChange,
38
44
  updateCacheWithRepository
39
45
  };