gh-manager-cli 1.38.0 → 1.38.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,10 @@
1
+ ## [1.38.1](https://github.com/wiiiimm/gh-manager-cli/compare/v1.38.0...v1.38.1) (2026-06-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add archive filter toggle for repositories [semantic pr title] ([#43](https://github.com/wiiiimm/gh-manager-cli/issues/43)) ([3b717a3](https://github.com/wiiiimm/gh-manager-cli/commit/3b717a345c6104a6b59e530eb7688bac9d8276ee))
7
+
1
8
  # [1.38.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.37.0...v1.38.0) (2025-09-09)
2
9
 
3
10
 
package/README.md CHANGED
@@ -76,7 +76,8 @@ On first run, you'll be prompted to authenticate with GitHub (OAuth recommended)
76
76
  - **Live Pagination**: Infinite scroll with automatic page prefetching
77
77
  - **Interactive Sorting**: Modal-based sort selection (updated, pushed, name, stars) with modal-based direction selection
78
78
  - **Smart Search**: Server-side search through repository names and descriptions (3+ characters)
79
- - **Visibility Filtering**: Modal-based visibility filter (All, Public, Private/Internal for enterprise) with smart filtering
79
+ - **Visibility Filter**: Modal-based visibility filter (All, Public, Private/Internal for enterprise) with smart filtering
80
+ - **Archive Filter**: Toggle-based archive filter (`A` key cycles All → Unarchived → Archived) for quick filtering by archive status
80
81
  - **Fork Status Tracking**: Always shows commits behind upstream for forked repositories
81
82
  - **Stars Mode**: View and manage starred repositories (personal account only)
82
83
  - **Repository Actions**:
@@ -102,7 +103,7 @@ On first run, you'll be prompted to authenticate with GitHub (OAuth recommended)
102
103
  - **Rate Limit Monitoring**: Dual API rate limit display (GraphQL & REST) with real-time usage deltas and visual warnings
103
104
 
104
105
  ### Technical Features
105
- - **Preference Persistence**: UI settings (sort, density, visibility filter, fork tracking) saved between sessions
106
+ - **Preference Persistence**: UI settings (sort, density, visibility filter, archive filter, fork tracking) saved between sessions
106
107
  - **Server-side Filtering**: Visibility filtering performed at GitHub API level for accurate pagination
107
108
  - **Cross-platform**: Works on macOS, Linux, and Windows
108
109
  - **Secure Storage**: Token stored with proper file permissions (0600)
@@ -271,6 +272,7 @@ Launch the app, then use the keys below:
271
272
  - **Display Density**: `T` to toggle compact/cozy/comfy
272
273
  - **Fork Status**: Always enabled - shows commits behind upstream for all forks
273
274
  - **Visibility Filter**: `V` opens modal (All, Public, Private/Internal for enterprise)
275
+ - **Archive Filter**: `A` toggles archive filter (All → Unarchived → Archived)
274
276
  - **Stars Mode**: `Shift+S` (personal account only) to view starred repositories
275
277
 
276
278
  ### Navigation & Account
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var require_package = __commonJS({
34
34
  "package.json"(exports, module) {
35
35
  module.exports = {
36
36
  name: "gh-manager-cli",
37
- version: "1.38.0",
37
+ version: "1.38.1",
38
38
  private: false,
39
39
  description: "TUI terminal app to manage GitHub repos. Clean up your account in 5 minutes. Archive, delete, rename repos with keyboard shortcuts. Alternative to clicking through github.com",
40
40
  license: "MIT",
@@ -115,7 +115,7 @@ var require_package = __commonJS({
115
115
  "semantic-release": "^24.2.7",
116
116
  tsup: "^8.5.0",
117
117
  typescript: "^5.9.2",
118
- vitest: "^2.1.3"
118
+ vitest: "^4.1.0"
119
119
  },
120
120
  repository: {
121
121
  type: "git",
@@ -1819,6 +1819,7 @@ function RepoListHeader({
1819
1819
  searchActive,
1820
1820
  searchLoading,
1821
1821
  visibilityFilter = "all",
1822
+ archiveFilter = "all",
1822
1823
  isEnterprise = false,
1823
1824
  starsMode = false
1824
1825
  }) {
@@ -1841,6 +1842,10 @@ function RepoListHeader({
1841
1842
  "Visibility: ",
1842
1843
  visibilityLabel
1843
1844
  ] }),
1845
+ archiveFilter !== "all" && /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
1846
+ "Archive: ",
1847
+ archiveFilter === "archived" ? "Archived" : "Unarchived"
1848
+ ] }),
1844
1849
  filter && !searchActive && /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
1845
1850
  'Filter: "',
1846
1851
  filter,
@@ -2362,6 +2367,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
2362
2367
  const [forkTracking, setForkTracking] = useState15(true);
2363
2368
  const [visibilityFilter, setVisibilityFilter] = useState15("all");
2364
2369
  const previousVisibilityFilter = useRef("all");
2370
+ const [archiveFilter, setArchiveFilter] = useState15("all");
2365
2371
  const sortFieldMap = {
2366
2372
  "updated": "UPDATED_AT",
2367
2373
  "pushed": "PUSHED_AT",
@@ -2534,6 +2540,9 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
2534
2540
  if (ui.visibilityFilter && ["all", "public", "private", "internal"].includes(ui.visibilityFilter)) {
2535
2541
  setVisibilityFilter(ui.visibilityFilter);
2536
2542
  }
2543
+ if (ui.archiveFilter && ["all", "unarchived", "archived"].includes(ui.archiveFilter)) {
2544
+ setArchiveFilter(ui.archiveFilter);
2545
+ }
2537
2546
  if (ui.ownerContext) {
2538
2547
  setOwnerContext(ui.ownerContext);
2539
2548
  if (onOrgContextChange) {
@@ -3064,6 +3073,15 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3064
3073
  });
3065
3074
  return;
3066
3075
  }
3076
+ if (input && input.toUpperCase() === "A" && !key.ctrl) {
3077
+ setArchiveFilter((f) => {
3078
+ const next = f === "all" ? "unarchived" : f === "unarchived" ? "archived" : "all";
3079
+ storeUIPrefs({ archiveFilter: next });
3080
+ return next;
3081
+ });
3082
+ setCursor(0);
3083
+ return;
3084
+ }
3067
3085
  if (input && input.toUpperCase() === "V") {
3068
3086
  if (!starsMode) {
3069
3087
  setVisibilityMode(true);
@@ -3076,6 +3094,11 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3076
3094
  if (visibilityFilter === "private") {
3077
3095
  result = result.filter((r) => r.visibility === "PRIVATE" || r.visibility === "INTERNAL");
3078
3096
  }
3097
+ if (archiveFilter === "archived") {
3098
+ result = result.filter((r) => r.isArchived);
3099
+ } else if (archiveFilter === "unarchived") {
3100
+ result = result.filter((r) => !r.isArchived);
3101
+ }
3079
3102
  const q = filter.trim().toLowerCase();
3080
3103
  if (q) {
3081
3104
  result = result.filter(
@@ -3083,7 +3106,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3083
3106
  );
3084
3107
  }
3085
3108
  return result;
3086
- }, [items, filter, visibilityFilter]);
3109
+ }, [items, filter, visibilityFilter, archiveFilter]);
3087
3110
  const filteredAndSorted = useMemo(() => {
3088
3111
  const arr = [...filtered];
3089
3112
  const dir = sortDir === "asc" ? 1 : -1;
@@ -3112,15 +3135,28 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3112
3135
  } else if (visibilityFilter === "public") {
3113
3136
  result = result.filter((r) => r.visibility === "PUBLIC");
3114
3137
  }
3138
+ if (archiveFilter === "archived") {
3139
+ result = result.filter((r) => r.isArchived);
3140
+ } else if (archiveFilter === "unarchived") {
3141
+ result = result.filter((r) => !r.isArchived);
3142
+ }
3115
3143
  return result;
3116
- }, [searchItems, visibilityFilter]);
3144
+ }, [searchItems, visibilityFilter, archiveFilter]);
3117
3145
  const filteredStarredItems = useMemo(() => {
3118
- if (!filter || filter.trim().length === 0) return starredItems;
3119
- const lowerFilter = filter.toLowerCase();
3120
- return starredItems.filter(
3121
- (repo) => repo.nameWithOwner.toLowerCase().includes(lowerFilter) || repo.description && repo.description.toLowerCase().includes(lowerFilter)
3122
- );
3123
- }, [starredItems, filter]);
3146
+ let result = starredItems;
3147
+ if (filter && filter.trim().length > 0) {
3148
+ const lowerFilter = filter.toLowerCase();
3149
+ result = result.filter(
3150
+ (repo) => repo.nameWithOwner.toLowerCase().includes(lowerFilter) || repo.description && repo.description.toLowerCase().includes(lowerFilter)
3151
+ );
3152
+ }
3153
+ if (archiveFilter === "archived") {
3154
+ result = result.filter((r) => r.isArchived);
3155
+ } else if (archiveFilter === "unarchived") {
3156
+ result = result.filter((r) => !r.isArchived);
3157
+ }
3158
+ return result;
3159
+ }, [starredItems, filter, archiveFilter]);
3124
3160
  const visibleItems = starsMode ? filteredStarredItems : searchActive ? filteredSearchItems : filteredAndSorted;
3125
3161
  useEffect11(() => {
3126
3162
  if (searchActive) {
@@ -3151,23 +3187,26 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3151
3187
  useEffect11(() => {
3152
3188
  const prefetchThreshold = Math.floor(visibleItems.length * 0.8);
3153
3189
  const nearEnd = visibleItems.length > 0 && cursor >= prefetchThreshold;
3190
+ const rawItemsLength = starsMode ? starredItems.length : searchActive ? searchItems.length : items.length;
3191
+ const filterDrainedPage = visibleItems.length === 0 && archiveFilter !== "all" && rawItemsLength > 0;
3192
+ const shouldFetch = nearEnd || filterDrainedPage;
3154
3193
  if (starsMode) {
3155
- if (!starredLoading && starredHasNextPage && nearEnd) {
3194
+ if (!starredLoading && starredHasNextPage && shouldFetch) {
3156
3195
  addDebugMessage(`[Infinite Scroll] Prefetching starred repos at ${cursor}/${visibleItems.length} (80% threshold: ${prefetchThreshold})`);
3157
3196
  fetchStarredRepositories(starredEndCursor);
3158
3197
  }
3159
3198
  } else if (searchActive) {
3160
- if (!searchLoading && searchHasNextPage && nearEnd) {
3199
+ if (!searchLoading && searchHasNextPage && shouldFetch) {
3161
3200
  addDebugMessage(`[Infinite Scroll] Prefetching search results at ${cursor}/${visibleItems.length} (80% threshold: ${prefetchThreshold})`);
3162
3201
  fetchSearchPage(searchEndCursor);
3163
3202
  }
3164
3203
  } else {
3165
- if (!loading && !loadingMore && hasNextPage && nearEnd) {
3204
+ if (!loading && !loadingMore && hasNextPage && shouldFetch) {
3166
3205
  addDebugMessage(`[Infinite Scroll] Prefetching repos at ${cursor}/${visibleItems.length} (80% threshold: ${prefetchThreshold})`);
3167
3206
  fetchPage(endCursor);
3168
3207
  }
3169
3208
  }
3170
- }, [cursor, visibleItems.length, starsMode, starredLoading, starredHasNextPage, starredEndCursor, searchActive, searchLoading, searchHasNextPage, searchEndCursor, loading, loadingMore, hasNextPage, endCursor]);
3209
+ }, [cursor, visibleItems.length, archiveFilter, items.length, starredItems.length, searchItems.length, starsMode, starredLoading, starredHasNextPage, starredEndCursor, searchActive, searchLoading, searchHasNextPage, searchEndCursor, loading, loadingMore, hasNextPage, endCursor]);
3171
3210
  function openInBrowser(url) {
3172
3211
  const platform = process.platform;
3173
3212
  const cmd = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
@@ -3646,6 +3685,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3646
3685
  searchActive,
3647
3686
  searchLoading,
3648
3687
  visibilityFilter,
3688
+ archiveFilter,
3649
3689
  isEnterprise: isEnterpriseOrg,
3650
3690
  starsMode
3651
3691
  }
@@ -3719,8 +3759,8 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
3719
3759
  /* @__PURE__ */ jsxs18(Box18, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
3720
3760
  /* @__PURE__ */ jsx19(Box18, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx19(Text19, { color: "gray", dimColor: modalOpen ? true : void 0, children: "\u2191\u2193 Navigate \u2022 Ctrl+G Top \u2022 G Bottom \u2022 \u23CE/O Open \u2022 R Refresh" }) }),
3721
3761
  /* @__PURE__ */ jsx19(Box18, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsxs18(Text19, { color: "gray", dimColor: modalOpen ? true : void 0, children: [
3722
- "/ Search \u2022 S Sort \u2022 D Direction \u2022 T Density",
3723
- !starsMode && " \u2022 V Visibility",
3762
+ "/ Search \u2022 S Sort \u2022 D Direction \u2022 T Density \u2022 A Archive",
3763
+ !starsMode && " \u2022 V Visibility Filter",
3724
3764
  ownerContext === "personal" && " \u2022 Shift+S Stars"
3725
3765
  ] }) }),
3726
3766
  /* @__PURE__ */ jsx19(Box18, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx19(Text19, { color: "gray", dimColor: modalOpen ? true : void 0, children: starsMode ? "I Info \u2022 C Copy URL \u2022 U Unstar Repository" : "I Info \u2022 C Copy URL \u2022 Ctrl+S Un/Star \u2022 Ctrl+R Rename \u2022 Ctrl+A Un/Archive \u2022 Ctrl+V Change Visibility \u2022 Ctrl+F Sync Fork" }) }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gh-manager-cli",
3
- "version": "1.38.0",
3
+ "version": "1.38.1",
4
4
  "private": false,
5
5
  "description": "TUI terminal app to manage GitHub repos. Clean up your account in 5 minutes. Archive, delete, rename repos with keyboard shortcuts. Alternative to clicking through github.com",
6
6
  "license": "MIT",
@@ -81,7 +81,7 @@
81
81
  "semantic-release": "^24.2.7",
82
82
  "tsup": "^8.5.0",
83
83
  "typescript": "^5.9.2",
84
- "vitest": "^2.1.3"
84
+ "vitest": "^4.1.0"
85
85
  },
86
86
  "repository": {
87
87
  "type": "git",