gh-manager-cli 1.6.1 → 1.6.2
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 +27 -16
- package/dist/index.js +392 -119
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.6.2](https://github.com/wiiiimm/gh-manager-cli/compare/v1.6.1...v1.6.2) (2025-08-31)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* resolve ES module import issues and improve search UX ([2a08c6b](https://github.com/wiiiimm/gh-manager-cli/commit/2a08c6b557c5a00e1005136074273e38c817a4c3))
|
|
7
|
+
|
|
1
8
|
## [1.6.1](https://github.com/wiiiimm/gh-manager-cli/compare/v1.6.0...v1.6.1) (2025-08-31)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ On first run, you'll be prompted for a GitHub Personal Access Token.
|
|
|
39
39
|
- **Repository Listing**: Browse all your personal repositories with metadata (stars, forks, language, etc.)
|
|
40
40
|
- **Live Pagination**: Infinite scroll with automatic page prefetching
|
|
41
41
|
- **Real-time Sorting**: Server-side sorting by updated, pushed, name, or stars (with direction toggle)
|
|
42
|
-
- **Smart
|
|
42
|
+
- **Smart Search**: Server-side search through repository names and descriptions (3+ characters)
|
|
43
43
|
- **Repository Actions**:
|
|
44
44
|
- View detailed info (`I`) - Shows repository metadata, language, size, and timestamps
|
|
45
45
|
- Open in browser (Enter/`O`)
|
|
@@ -137,7 +137,9 @@ Launch the app, then use the keys below:
|
|
|
137
137
|
|
|
138
138
|
- Navigation: Up/Down, PageUp/PageDown, `Ctrl+G` (top), `G` (bottom)
|
|
139
139
|
- Refresh: `R`
|
|
140
|
-
-
|
|
140
|
+
- Search: `/` to enter search mode, type 3+ characters for server-side search
|
|
141
|
+
- Down arrow or Enter: Start browsing search results
|
|
142
|
+
- Esc: Clear search and return to full repository list
|
|
141
143
|
- Sorting: `S` to cycle field (updated → pushed → name → stars → forks), `D` to toggle direction
|
|
142
144
|
- Display density: `T` to toggle compact/cozy/comfy
|
|
143
145
|
- Repository info: `I` to view detailed metadata (size, language, timestamps)
|
|
@@ -152,7 +154,7 @@ Launch the app, then use the keys below:
|
|
|
152
154
|
- Logout: `Ctrl+L`
|
|
153
155
|
- Toggle fork metrics: `F`
|
|
154
156
|
- Quit: `Q`
|
|
155
|
-
- Esc: cancels modals or
|
|
157
|
+
- Esc: cancels modals, clears search, or returns to normal listing (does not quit)
|
|
156
158
|
|
|
157
159
|
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.
|
|
158
160
|
|
|
@@ -186,7 +188,7 @@ Notes:
|
|
|
186
188
|
Project layout:
|
|
187
189
|
- `src/index.tsx` — CLI entry and error handling
|
|
188
190
|
- `src/ui/App.tsx` — token bootstrap, renders `RepoList`
|
|
189
|
-
- `src/ui/RepoList.tsx` — list UI, keys,
|
|
191
|
+
- `src/ui/RepoList.tsx` — list UI, keys, search, sorting, infinite scroll
|
|
190
192
|
- `src/github.ts` — GraphQL client and queries (repos + rateLimit)
|
|
191
193
|
- `src/config.ts` — token read/write and file perms
|
|
192
194
|
- `src/types.ts` — shared types
|
|
@@ -195,22 +197,31 @@ Project layout:
|
|
|
195
197
|
|
|
196
198
|
gh-manager-cli includes built-in Apollo Client caching to reduce GitHub API calls and improve performance. Caching is **always enabled** for optimal performance.
|
|
197
199
|
|
|
198
|
-
###
|
|
200
|
+
### Debug Mode
|
|
201
|
+
|
|
202
|
+
Run with `GH_MANAGER_DEBUG=1` to enable debugging features:
|
|
203
|
+
```bash
|
|
204
|
+
GH_MANAGER_DEBUG=1 npx gh-manager
|
|
205
|
+
```
|
|
199
206
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
Debug mode provides:
|
|
208
|
+
- **Apollo performance metrics**: Query execution time, cache hit/miss indicators
|
|
209
|
+
- **Detailed error messages**: Full GraphQL and network errors for troubleshooting
|
|
210
|
+
- **Data source tracking**: Shows whether data came from cache or network
|
|
204
211
|
|
|
205
|
-
|
|
206
|
-
- Shows cache file size and age
|
|
207
|
-
- Lists recent cache entries with timestamps
|
|
208
|
-
- Displays cache directory location
|
|
212
|
+
### Verifying Cache is Working
|
|
209
213
|
|
|
210
|
-
|
|
214
|
+
1. **Performance Indicators** (visible in debug mode):
|
|
211
215
|
- **From cache: YES** = Data served from cache
|
|
212
216
|
- **Query time < 50ms** = Likely cache hit
|
|
213
|
-
- **
|
|
217
|
+
- **Network status codes** = Shows Apollo's internal cache state
|
|
218
|
+
|
|
219
|
+
2. **API Credits**: Monitor the API counter in the header - it should remain stable when navigating previously loaded data
|
|
220
|
+
|
|
221
|
+
3. **Cache Inspection**: Press `Ctrl+I` (available anytime) to see:
|
|
222
|
+
- Cache file location and size
|
|
223
|
+
- Recent cache entries with timestamps
|
|
224
|
+
- Cache age for each query type
|
|
214
225
|
|
|
215
226
|
### Why API Credits Might Still Decrease
|
|
216
227
|
|
|
@@ -246,7 +257,7 @@ Highlights on deck:
|
|
|
246
257
|
- Density toggle for row spacing (compact/cozy/comfy)
|
|
247
258
|
- Repo actions (archive/unarchive, delete) with confirmations
|
|
248
259
|
- Organization support and switching
|
|
249
|
-
-
|
|
260
|
+
- Enhanced server-side search with improved UX
|
|
250
261
|
- Optional OS keychain storage via `keytar`
|
|
251
262
|
|
|
252
263
|
## License
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var require_package = __commonJS({
|
|
|
9
9
|
"package.json"(exports, module) {
|
|
10
10
|
module.exports = {
|
|
11
11
|
name: "gh-manager-cli",
|
|
12
|
-
version: "1.6.
|
|
12
|
+
version: "1.6.2",
|
|
13
13
|
private: false,
|
|
14
14
|
description: "Interactive CLI to manage your GitHub repos (personal) with Ink",
|
|
15
15
|
license: "MIT",
|
|
@@ -47,12 +47,13 @@ var require_package = __commonJS({
|
|
|
47
47
|
node: ">=18"
|
|
48
48
|
},
|
|
49
49
|
dependencies: {
|
|
50
|
-
"@apollo/client": "^3.
|
|
50
|
+
"@apollo/client": "^3.14.0",
|
|
51
51
|
"@octokit/graphql": "^9.0.1",
|
|
52
52
|
"apollo3-cache-persist": "^0.14.1",
|
|
53
53
|
chalk: "^5.6.0",
|
|
54
54
|
dotenv: "^17.2.1",
|
|
55
55
|
"env-paths": "^3.0.0",
|
|
56
|
+
graphql: "^16.11.0",
|
|
56
57
|
ink: "^6.2.3",
|
|
57
58
|
"ink-spinner": "^5.0.0",
|
|
58
59
|
"ink-text-input": "^6.0.0",
|
|
@@ -187,67 +188,77 @@ function storeUIPrefs(patch) {
|
|
|
187
188
|
|
|
188
189
|
// src/github.ts
|
|
189
190
|
import { graphql as makeGraphQL } from "@octokit/graphql";
|
|
191
|
+
import { ApolloClient, InMemoryCache, HttpLink, gql } from "@apollo/client/core/index.js";
|
|
192
|
+
import { persistCache } from "apollo3-cache-persist";
|
|
193
|
+
import fs2 from "fs";
|
|
194
|
+
import path2 from "path";
|
|
195
|
+
import envPaths2 from "env-paths";
|
|
190
196
|
function makeClient(token) {
|
|
191
197
|
return makeGraphQL.defaults({
|
|
192
198
|
headers: { authorization: `token ${token}` }
|
|
193
199
|
});
|
|
194
200
|
}
|
|
195
201
|
async function makeApolloClient(token) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (process.platform !== "win32") {
|
|
223
|
-
try {
|
|
224
|
-
fs3.chmodSync(file, 384);
|
|
225
|
-
} catch {
|
|
202
|
+
try {
|
|
203
|
+
if (typeof globalThis.fetch === "undefined") {
|
|
204
|
+
throw new Error("Fetch API not available. Node 18+ is required.");
|
|
205
|
+
}
|
|
206
|
+
const cache = new InMemoryCache();
|
|
207
|
+
const storage = {
|
|
208
|
+
async getItem(key) {
|
|
209
|
+
try {
|
|
210
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
211
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
212
|
+
return fs2.readFileSync(file, "utf8");
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
async setItem(key, value) {
|
|
218
|
+
try {
|
|
219
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
220
|
+
fs2.mkdirSync(p, { recursive: true });
|
|
221
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
222
|
+
fs2.writeFileSync(file, value, "utf8");
|
|
223
|
+
if (process.platform !== "win32") {
|
|
224
|
+
try {
|
|
225
|
+
fs2.chmodSync(file, 384);
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
226
228
|
}
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
async removeItem(key) {
|
|
233
|
+
try {
|
|
234
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
235
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
236
|
+
fs2.unlinkSync(file);
|
|
237
|
+
} catch {
|
|
227
238
|
}
|
|
228
|
-
} catch {
|
|
229
239
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
};
|
|
241
|
+
await persistCache({ cache, storage, debounce: 500, maxSize: 5 * 1024 * 1024 });
|
|
242
|
+
const link = new HttpLink({
|
|
243
|
+
uri: "https://api.github.com/graphql",
|
|
244
|
+
fetch: globalThis.fetch,
|
|
245
|
+
headers: { authorization: `Bearer ${token}` }
|
|
246
|
+
});
|
|
247
|
+
const client = new ApolloClient({ cache, link });
|
|
248
|
+
return { client, gql };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const debug = process.env.GH_MANAGER_DEBUG === "1";
|
|
251
|
+
if (debug) {
|
|
252
|
+
process.stderr.write(`
|
|
253
|
+
\u274C Failed to initialize Apollo Client: ${error.message}
|
|
254
|
+
`);
|
|
255
|
+
if (error.stack) {
|
|
256
|
+
process.stderr.write(`Stack: ${error.stack}
|
|
257
|
+
`);
|
|
240
258
|
}
|
|
241
259
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const link = new HttpLink({
|
|
245
|
-
uri: "https://api.github.com/graphql",
|
|
246
|
-
fetch: globalThis.fetch,
|
|
247
|
-
headers: { authorization: `Bearer ${token}` }
|
|
248
|
-
});
|
|
249
|
-
const client = new ApolloClient({ cache, link });
|
|
250
|
-
return { client, gql };
|
|
260
|
+
throw new Error(`Apollo Client initialization failed: ${error.message}`);
|
|
261
|
+
}
|
|
251
262
|
}
|
|
252
263
|
async function getViewerLogin(client) {
|
|
253
264
|
const query = (
|
|
@@ -432,6 +443,73 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
432
443
|
const octo = makeClient(token);
|
|
433
444
|
return fetchViewerReposPage(octo, first, after, orderBy, includeForkTracking);
|
|
434
445
|
}
|
|
446
|
+
async function searchRepositoriesUnified(token, viewer, text, first, after, sortKey = "UPDATED_AT", sortDir = "DESC", includeForkTracking = true, fetchPolicy = "network-only") {
|
|
447
|
+
const q = `${text} user:${viewer} in:name,description fork:true`;
|
|
448
|
+
try {
|
|
449
|
+
const ap = await makeApolloClient(token);
|
|
450
|
+
const queryDoc = ap.gql`
|
|
451
|
+
query SearchRepos($q: String!, $first: Int!, $after: String) {
|
|
452
|
+
rateLimit { limit remaining resetAt }
|
|
453
|
+
search(query: $q, type: REPOSITORY, first: $first, after: $after) {
|
|
454
|
+
repositoryCount
|
|
455
|
+
pageInfo { endCursor hasNextPage }
|
|
456
|
+
nodes {
|
|
457
|
+
... on Repository {
|
|
458
|
+
id
|
|
459
|
+
name
|
|
460
|
+
nameWithOwner
|
|
461
|
+
description
|
|
462
|
+
visibility
|
|
463
|
+
isPrivate
|
|
464
|
+
isFork
|
|
465
|
+
isArchived
|
|
466
|
+
stargazerCount
|
|
467
|
+
forkCount
|
|
468
|
+
primaryLanguage { name color }
|
|
469
|
+
updatedAt
|
|
470
|
+
pushedAt
|
|
471
|
+
diskUsage
|
|
472
|
+
${includeForkTracking ? `
|
|
473
|
+
parent { nameWithOwner defaultBranchRef { name target { ... on Commit { history(first: 0) { totalCount } } } } }
|
|
474
|
+
defaultBranchRef { name target { ... on Commit { history(first: 0) { totalCount } } } }` : `
|
|
475
|
+
parent { nameWithOwner }
|
|
476
|
+
defaultBranchRef { name }`}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
`;
|
|
482
|
+
const res = await ap.client.query({
|
|
483
|
+
query: queryDoc,
|
|
484
|
+
variables: { q, first, after: after ?? null },
|
|
485
|
+
fetchPolicy
|
|
486
|
+
});
|
|
487
|
+
const data = res.data.search;
|
|
488
|
+
return {
|
|
489
|
+
nodes: data.nodes,
|
|
490
|
+
endCursor: data.pageInfo.endCursor,
|
|
491
|
+
hasNextPage: data.pageInfo.hasNextPage,
|
|
492
|
+
totalCount: data.repositoryCount,
|
|
493
|
+
rateLimit: res.data.rateLimit
|
|
494
|
+
};
|
|
495
|
+
} catch (e) {
|
|
496
|
+
const debug = process.env.GH_MANAGER_DEBUG === "1";
|
|
497
|
+
if (debug) {
|
|
498
|
+
process.stderr.write(`
|
|
499
|
+
\u274C Search failed: ${e.message}
|
|
500
|
+
`);
|
|
501
|
+
if (e.graphQLErrors) {
|
|
502
|
+
process.stderr.write(`GraphQL errors: ${JSON.stringify(e.graphQLErrors, null, 2)}
|
|
503
|
+
`);
|
|
504
|
+
}
|
|
505
|
+
if (e.networkError) {
|
|
506
|
+
process.stderr.write(`Network error: ${e.networkError.message}
|
|
507
|
+
`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
throw e;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
435
513
|
async function deleteRepositoryRest(token, owner, repo) {
|
|
436
514
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
437
515
|
const res = await fetch(url, {
|
|
@@ -510,21 +588,21 @@ async function syncForkWithUpstream(token, owner, repo, branch = "main") {
|
|
|
510
588
|
}
|
|
511
589
|
async function purgeApolloCacheFiles() {
|
|
512
590
|
try {
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
const p =
|
|
517
|
-
const cacheFile =
|
|
518
|
-
const metaFile =
|
|
591
|
+
const fs4 = await import("fs");
|
|
592
|
+
const path4 = await import("path");
|
|
593
|
+
const envPaths4 = (await import("env-paths")).default;
|
|
594
|
+
const p = envPaths4("gh-manager-cli").data;
|
|
595
|
+
const cacheFile = path4.join(p, "apollo-cache.json");
|
|
596
|
+
const metaFile = path4.join(p, "apollo-cache-meta.json");
|
|
519
597
|
if (process.env.GH_MANAGER_DEBUG === "1") {
|
|
520
598
|
console.log(`\u{1F5D1}\uFE0F Purging cache files from: ${p}`);
|
|
521
599
|
}
|
|
522
600
|
try {
|
|
523
|
-
|
|
601
|
+
fs4.unlinkSync(cacheFile);
|
|
524
602
|
} catch {
|
|
525
603
|
}
|
|
526
604
|
try {
|
|
527
|
-
|
|
605
|
+
fs4.unlinkSync(metaFile);
|
|
528
606
|
} catch {
|
|
529
607
|
}
|
|
530
608
|
} catch {
|
|
@@ -532,17 +610,17 @@ async function purgeApolloCacheFiles() {
|
|
|
532
610
|
}
|
|
533
611
|
async function inspectCacheStatus() {
|
|
534
612
|
try {
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
const
|
|
538
|
-
const p =
|
|
539
|
-
const cacheFile =
|
|
540
|
-
const metaFile =
|
|
613
|
+
const fs4 = await import("fs");
|
|
614
|
+
const path4 = await import("path");
|
|
615
|
+
const envPaths4 = (await import("env-paths")).default;
|
|
616
|
+
const p = envPaths4("gh-manager-cli").data;
|
|
617
|
+
const cacheFile = path4.join(p, "apollo-cache.json");
|
|
618
|
+
const metaFile = path4.join(p, "apollo-cache-meta.json");
|
|
541
619
|
process.stderr.write(`
|
|
542
620
|
\u{1F4C2} Cache directory: ${p}
|
|
543
621
|
`);
|
|
544
622
|
try {
|
|
545
|
-
const cacheStats =
|
|
623
|
+
const cacheStats = fs4.statSync(cacheFile);
|
|
546
624
|
process.stderr.write(`\u{1F4BE} Cache file: ${Math.round(cacheStats.size / 1024)}KB (${cacheStats.mtime.toISOString()})
|
|
547
625
|
`);
|
|
548
626
|
} catch {
|
|
@@ -550,8 +628,8 @@ async function inspectCacheStatus() {
|
|
|
550
628
|
`);
|
|
551
629
|
}
|
|
552
630
|
try {
|
|
553
|
-
const metaStats =
|
|
554
|
-
const metaContent =
|
|
631
|
+
const metaStats = fs4.statSync(metaFile);
|
|
632
|
+
const metaContent = fs4.readFileSync(metaFile, "utf8");
|
|
555
633
|
const meta = JSON.parse(metaContent);
|
|
556
634
|
process.stderr.write(`\u{1F4CA} Meta file: ${Object.keys(meta.fetched || {}).length} entries (${metaStats.mtime.toISOString()})
|
|
557
635
|
`);
|
|
@@ -576,22 +654,22 @@ async function inspectCacheStatus() {
|
|
|
576
654
|
}
|
|
577
655
|
|
|
578
656
|
// src/ui/RepoList.tsx
|
|
579
|
-
import { useEffect, useMemo, useState } from "react";
|
|
657
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
580
658
|
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
581
659
|
import TextInput from "ink-text-input";
|
|
582
660
|
import chalk from "chalk";
|
|
583
661
|
|
|
584
662
|
// src/apolloMeta.ts
|
|
585
|
-
import
|
|
586
|
-
import
|
|
587
|
-
import
|
|
588
|
-
var paths2 =
|
|
663
|
+
import fs3 from "fs";
|
|
664
|
+
import path3 from "path";
|
|
665
|
+
import envPaths3 from "env-paths";
|
|
666
|
+
var paths2 = envPaths3("gh-manager-cli");
|
|
589
667
|
var dataDir = paths2.data;
|
|
590
|
-
var metaPath =
|
|
668
|
+
var metaPath = path3.join(dataDir, "apollo-cache-meta.json");
|
|
591
669
|
var VERSION = 1;
|
|
592
670
|
function readMeta() {
|
|
593
671
|
try {
|
|
594
|
-
const raw =
|
|
672
|
+
const raw = fs3.readFileSync(metaPath, "utf8");
|
|
595
673
|
const parsed = JSON.parse(raw);
|
|
596
674
|
if (parsed && typeof parsed === "object" && parsed.fetched) return parsed;
|
|
597
675
|
} catch {
|
|
@@ -600,11 +678,11 @@ function readMeta() {
|
|
|
600
678
|
}
|
|
601
679
|
function writeMeta(meta) {
|
|
602
680
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
681
|
+
fs3.mkdirSync(dataDir, { recursive: true });
|
|
682
|
+
fs3.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf8");
|
|
605
683
|
if (process.platform !== "win32") {
|
|
606
684
|
try {
|
|
607
|
-
|
|
685
|
+
fs3.chmodSync(metaPath, 384);
|
|
608
686
|
} catch {
|
|
609
687
|
}
|
|
610
688
|
}
|
|
@@ -615,6 +693,11 @@ function makeApolloKey(opts) {
|
|
|
615
693
|
const v = opts.viewer || "unknown";
|
|
616
694
|
return `viewer:${v}|sort:${opts.sortKey}:${opts.sortDir}|ps:${opts.pageSize}|forks:${opts.forkTracking ? "1" : "0"}`;
|
|
617
695
|
}
|
|
696
|
+
function makeSearchKey(opts) {
|
|
697
|
+
const v = opts.viewer || "unknown";
|
|
698
|
+
const query = (opts.q || "").trim().toLowerCase();
|
|
699
|
+
return `search:${query}|viewer:${v}|sort:${opts.sortKey}:${opts.sortDir}|ps:${opts.pageSize}|forks:${opts.forkTracking ? "1" : "0"}`;
|
|
700
|
+
}
|
|
618
701
|
function isFresh(key, ttlMs = Number(process.env.APOLLO_TTL_MS || 30 * 60 * 1e3)) {
|
|
619
702
|
const meta = readMeta();
|
|
620
703
|
const iso = meta.fetched[key];
|
|
@@ -699,6 +782,15 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
699
782
|
const { exit } = useApp();
|
|
700
783
|
const { stdout } = useStdout();
|
|
701
784
|
const client = useMemo(() => makeClient(token), [token]);
|
|
785
|
+
const [debugMessages, setDebugMessages] = useState([]);
|
|
786
|
+
const addDebugMessage = (msg) => {
|
|
787
|
+
if (process.env.GH_MANAGER_DEBUG === "1") {
|
|
788
|
+
setDebugMessages((prev) => [...prev.slice(-9), msg]);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
React.useEffect(() => {
|
|
792
|
+
addDebugMessage(`[RepoList] Component mounted`);
|
|
793
|
+
}, []);
|
|
702
794
|
const terminalWidth = stdout?.columns ?? 80;
|
|
703
795
|
const availableHeight = maxVisibleRows ?? 20;
|
|
704
796
|
const [items, setItems] = useState([]);
|
|
@@ -715,6 +807,11 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
715
807
|
const [prevRateLimit, setPrevRateLimit] = useState(void 0);
|
|
716
808
|
const [density, setDensity] = useState(2);
|
|
717
809
|
const [prefsLoaded, setPrefsLoaded] = useState(false);
|
|
810
|
+
const [searchItems, setSearchItems] = useState([]);
|
|
811
|
+
const [searchEndCursor, setSearchEndCursor] = useState(null);
|
|
812
|
+
const [searchHasNextPage, setSearchHasNextPage] = useState(false);
|
|
813
|
+
const [searchTotalCount, setSearchTotalCount] = useState(0);
|
|
814
|
+
const [searchLoading, setSearchLoading] = useState(false);
|
|
718
815
|
const [deleteMode, setDeleteMode] = useState(false);
|
|
719
816
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
720
817
|
const [deleteCode, setDeleteCode] = useState("");
|
|
@@ -774,7 +871,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
774
871
|
setDeleteError(null);
|
|
775
872
|
setDeleting(false);
|
|
776
873
|
setDeleteConfirmStage(false);
|
|
777
|
-
setCursor((c) => Math.max(0, Math.min(c,
|
|
874
|
+
setCursor((c) => Math.max(0, Math.min(c, visibleItems.length - 2)));
|
|
778
875
|
} catch (e) {
|
|
779
876
|
setDeleting(false);
|
|
780
877
|
setDeleteError("Failed to delete repository. Ensure delete_repo scope and admin permissions.");
|
|
@@ -843,6 +940,62 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
843
940
|
setLoadingMore(false);
|
|
844
941
|
}
|
|
845
942
|
};
|
|
943
|
+
const fetchSearchPage = async (after, reset = false, policy, searchQuery) => {
|
|
944
|
+
const query = searchQuery ?? filter;
|
|
945
|
+
addDebugMessage(`[fetchSearchPage] query="${query}", searchQuery="${searchQuery}", filter="${filter}"`);
|
|
946
|
+
if (!viewerLogin) {
|
|
947
|
+
addDebugMessage("\u274C No viewerLogin for search");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
setSearchLoading(true);
|
|
951
|
+
try {
|
|
952
|
+
const orderBy = { field: sortFieldMap[sortKey], direction: sortDir.toUpperCase() };
|
|
953
|
+
addDebugMessage(`[fetchSearchPage] Calling API with viewer="${viewerLogin}", query="${query.trim()}"`);
|
|
954
|
+
const page = await searchRepositoriesUnified(
|
|
955
|
+
token,
|
|
956
|
+
viewerLogin,
|
|
957
|
+
query.trim(),
|
|
958
|
+
PAGE_SIZE,
|
|
959
|
+
after ?? null,
|
|
960
|
+
orderBy.field,
|
|
961
|
+
orderBy.direction,
|
|
962
|
+
forkTracking,
|
|
963
|
+
policy ?? (after ? "network-only" : "cache-first")
|
|
964
|
+
);
|
|
965
|
+
addDebugMessage(`[fetchSearchPage] API returned ${page.nodes.length} results, totalCount=${page.totalCount}`);
|
|
966
|
+
if (page.nodes.length > 0) {
|
|
967
|
+
addDebugMessage(`[fetchSearchPage] First result: ${page.nodes[0].name}`);
|
|
968
|
+
}
|
|
969
|
+
setSearchItems((prev) => reset || !after ? page.nodes : [...prev, ...page.nodes]);
|
|
970
|
+
setSearchEndCursor(page.endCursor);
|
|
971
|
+
setSearchHasNextPage(page.hasNextPage);
|
|
972
|
+
setSearchTotalCount(page.totalCount);
|
|
973
|
+
if (!after) {
|
|
974
|
+
try {
|
|
975
|
+
const key = makeSearchKey({
|
|
976
|
+
viewer: viewerLogin || "unknown",
|
|
977
|
+
q: query.trim(),
|
|
978
|
+
sortKey,
|
|
979
|
+
sortDir,
|
|
980
|
+
pageSize: PAGE_SIZE,
|
|
981
|
+
forkTracking
|
|
982
|
+
});
|
|
983
|
+
markFetched(key);
|
|
984
|
+
} catch {
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
setError(null);
|
|
988
|
+
} catch (e) {
|
|
989
|
+
const errorMsg = `Failed to search: ${e.message || e}`;
|
|
990
|
+
addDebugMessage(`\u274C Search error: ${e.message || e}`);
|
|
991
|
+
if (e.stack) {
|
|
992
|
+
addDebugMessage(`Stack: ${e.stack.split("\n")[0]}`);
|
|
993
|
+
}
|
|
994
|
+
setError(errorMsg);
|
|
995
|
+
} finally {
|
|
996
|
+
setSearchLoading(false);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
846
999
|
useEffect(() => {
|
|
847
1000
|
const ui = getUIPrefs();
|
|
848
1001
|
if (ui.density !== void 0) setDensity(ui.density);
|
|
@@ -867,28 +1020,65 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
867
1020
|
pageSize: PAGE_SIZE,
|
|
868
1021
|
forkTracking
|
|
869
1022
|
});
|
|
870
|
-
policy = isFresh(key) ? "cache-first" : "
|
|
1023
|
+
policy = isFresh(key) ? "cache-first" : "network-only";
|
|
871
1024
|
} catch {
|
|
872
1025
|
}
|
|
873
1026
|
fetchPage(null, true, false, void 0, policy);
|
|
874
1027
|
}, [client, prefsLoaded]);
|
|
875
1028
|
useEffect(() => {
|
|
876
|
-
if (
|
|
1029
|
+
if (!searchActive) {
|
|
1030
|
+
if (items.length > 0) {
|
|
1031
|
+
let policy = "cache-first";
|
|
1032
|
+
try {
|
|
1033
|
+
const key = makeApolloKey({
|
|
1034
|
+
viewer: viewerLogin || "unknown",
|
|
1035
|
+
sortKey,
|
|
1036
|
+
sortDir,
|
|
1037
|
+
pageSize: PAGE_SIZE,
|
|
1038
|
+
forkTracking
|
|
1039
|
+
});
|
|
1040
|
+
policy = isFresh(key) ? "cache-first" : "network-only";
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
fetchPage(null, true, true, void 0, policy);
|
|
1044
|
+
}
|
|
1045
|
+
} else {
|
|
1046
|
+
if (!searchLoading && filter.trim().length >= 3) {
|
|
1047
|
+
let policy = "cache-first";
|
|
1048
|
+
try {
|
|
1049
|
+
const key = makeSearchKey({
|
|
1050
|
+
viewer: viewerLogin || "unknown",
|
|
1051
|
+
q: filter.trim(),
|
|
1052
|
+
sortKey,
|
|
1053
|
+
sortDir,
|
|
1054
|
+
pageSize: PAGE_SIZE,
|
|
1055
|
+
forkTracking
|
|
1056
|
+
});
|
|
1057
|
+
policy = isFresh(key, 90 * 1e3) ? "cache-first" : "network-only";
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
fetchSearchPage(null, true, policy);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}, [sortKey, sortDir]);
|
|
1064
|
+
useEffect(() => {
|
|
1065
|
+
if (viewerLogin && searchActive && !searchLoading && searchItems.length === 0) {
|
|
877
1066
|
let policy = "cache-first";
|
|
878
1067
|
try {
|
|
879
|
-
const key =
|
|
1068
|
+
const key = makeSearchKey({
|
|
880
1069
|
viewer: viewerLogin || "unknown",
|
|
1070
|
+
q: filter.trim(),
|
|
881
1071
|
sortKey,
|
|
882
1072
|
sortDir,
|
|
883
1073
|
pageSize: PAGE_SIZE,
|
|
884
1074
|
forkTracking
|
|
885
1075
|
});
|
|
886
|
-
policy = isFresh(key) ? "cache-first" : "
|
|
1076
|
+
policy = isFresh(key, 90 * 1e3) ? "cache-first" : "network-only";
|
|
887
1077
|
} catch {
|
|
888
1078
|
}
|
|
889
|
-
|
|
1079
|
+
fetchSearchPage(null, true, policy);
|
|
890
1080
|
}
|
|
891
|
-
}, [
|
|
1081
|
+
}, [viewerLogin]);
|
|
892
1082
|
useInput((input, key) => {
|
|
893
1083
|
if (deleteMode) {
|
|
894
1084
|
if (key.escape || input && input.toUpperCase() === "C") {
|
|
@@ -1044,8 +1234,31 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1044
1234
|
if (filterMode) {
|
|
1045
1235
|
if (key.escape) {
|
|
1046
1236
|
setFilterMode(false);
|
|
1237
|
+
setFilter("");
|
|
1238
|
+
setSearchItems([]);
|
|
1239
|
+
setSearchEndCursor(null);
|
|
1240
|
+
setSearchHasNextPage(false);
|
|
1241
|
+
setSearchTotalCount(0);
|
|
1242
|
+
setCursor(0);
|
|
1243
|
+
addDebugMessage("[ESC] Cleared search and returned to normal listing");
|
|
1047
1244
|
return;
|
|
1048
1245
|
}
|
|
1246
|
+
if (key.downArrow && searchActive && visibleItems.length > 0) {
|
|
1247
|
+
setFilterMode(false);
|
|
1248
|
+
setCursor(0);
|
|
1249
|
+
addDebugMessage("[DOWN] Exited filter mode and selected first search result");
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (key.escape && searchActive) {
|
|
1255
|
+
setFilter("");
|
|
1256
|
+
setSearchItems([]);
|
|
1257
|
+
setSearchEndCursor(null);
|
|
1258
|
+
setSearchHasNextPage(false);
|
|
1259
|
+
setSearchTotalCount(0);
|
|
1260
|
+
setCursor(0);
|
|
1261
|
+
addDebugMessage("[ESC] Cleared search and returned to normal listing");
|
|
1049
1262
|
return;
|
|
1050
1263
|
}
|
|
1051
1264
|
if (input && input.toUpperCase() === "Q") {
|
|
@@ -1058,16 +1271,16 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1058
1271
|
exit();
|
|
1059
1272
|
return;
|
|
1060
1273
|
}
|
|
1061
|
-
if (key.downArrow) setCursor((c) => Math.min(c + 1,
|
|
1274
|
+
if (key.downArrow) setCursor((c) => Math.min(c + 1, visibleItems.length - 1));
|
|
1062
1275
|
if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
|
|
1063
|
-
if (key.pageDown) setCursor((c) => Math.min(c + 10,
|
|
1276
|
+
if (key.pageDown) setCursor((c) => Math.min(c + 10, visibleItems.length - 1));
|
|
1064
1277
|
if (key.pageUp) setCursor((c) => Math.max(c - 10, 0));
|
|
1065
1278
|
if (key.return) {
|
|
1066
|
-
const repo =
|
|
1279
|
+
const repo = visibleItems[cursor];
|
|
1067
1280
|
if (repo) openInBrowser(`https://github.com/${repo.nameWithOwner}`);
|
|
1068
1281
|
}
|
|
1069
1282
|
if (key.delete && !key.backspace || key.backspace && key.ctrl) {
|
|
1070
|
-
const repo =
|
|
1283
|
+
const repo = visibleItems[cursor];
|
|
1071
1284
|
if (repo) {
|
|
1072
1285
|
setDeleteTarget(repo);
|
|
1073
1286
|
setDeleteMode(true);
|
|
@@ -1086,7 +1299,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1086
1299
|
return;
|
|
1087
1300
|
}
|
|
1088
1301
|
if (!key.ctrl && input && input.toUpperCase() === "G") {
|
|
1089
|
-
setCursor(
|
|
1302
|
+
setCursor(visibleItems.length - 1);
|
|
1090
1303
|
return;
|
|
1091
1304
|
}
|
|
1092
1305
|
if (input && input.toUpperCase() === "R") {
|
|
@@ -1103,7 +1316,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1103
1316
|
})();
|
|
1104
1317
|
}
|
|
1105
1318
|
if (key.ctrl && (input === "a" || input === "A")) {
|
|
1106
|
-
const repo =
|
|
1319
|
+
const repo = visibleItems[cursor];
|
|
1107
1320
|
if (repo) {
|
|
1108
1321
|
setArchiveTarget(repo);
|
|
1109
1322
|
setArchiveMode(true);
|
|
@@ -1114,7 +1327,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1114
1327
|
return;
|
|
1115
1328
|
}
|
|
1116
1329
|
if (key.ctrl && (input === "u" || input === "U")) {
|
|
1117
|
-
const repo =
|
|
1330
|
+
const repo = visibleItems[cursor];
|
|
1118
1331
|
if (repo && repo.isFork && repo.parent) {
|
|
1119
1332
|
const hasCommitData = repo.defaultBranchRef && repo.parent.defaultBranchRef && repo.parent.defaultBranchRef.target?.history && repo.defaultBranchRef.target?.history;
|
|
1120
1333
|
const commitsBehind = hasCommitData ? repo.parent.defaultBranchRef.target.history.totalCount - repo.defaultBranchRef.target.history.totalCount : 0;
|
|
@@ -1132,12 +1345,13 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1132
1345
|
setLogoutFocus("confirm");
|
|
1133
1346
|
return;
|
|
1134
1347
|
}
|
|
1135
|
-
if (key.ctrl &&
|
|
1348
|
+
if (key.ctrl && (input === "i" || input === "I")) {
|
|
1136
1349
|
(async () => {
|
|
1137
1350
|
try {
|
|
1138
1351
|
await inspectCacheStatus();
|
|
1139
1352
|
} catch (e) {
|
|
1140
|
-
|
|
1353
|
+
process.stderr.write(`\u274C Failed to inspect cache: ${e.message}
|
|
1354
|
+
`);
|
|
1141
1355
|
}
|
|
1142
1356
|
})();
|
|
1143
1357
|
return;
|
|
@@ -1169,7 +1383,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1169
1383
|
return;
|
|
1170
1384
|
}
|
|
1171
1385
|
if (input && input.toUpperCase() === "O") {
|
|
1172
|
-
const repo =
|
|
1386
|
+
const repo = visibleItems[cursor];
|
|
1173
1387
|
if (repo) openInBrowser(`https://github.com/${repo.nameWithOwner}`);
|
|
1174
1388
|
return;
|
|
1175
1389
|
}
|
|
@@ -1197,11 +1411,6 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1197
1411
|
return;
|
|
1198
1412
|
}
|
|
1199
1413
|
});
|
|
1200
|
-
useEffect(() => {
|
|
1201
|
-
if (!loading && !loadingMore && hasNextPage && cursor >= filteredAndSorted.length - 5) {
|
|
1202
|
-
fetchPage(endCursor);
|
|
1203
|
-
}
|
|
1204
|
-
}, [cursor, hasNextPage, endCursor, loading, loadingMore]);
|
|
1205
1414
|
const filtered = useMemo(() => {
|
|
1206
1415
|
const q = filter.trim().toLowerCase();
|
|
1207
1416
|
if (!q) return items;
|
|
@@ -1229,9 +1438,16 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1229
1438
|
});
|
|
1230
1439
|
return arr;
|
|
1231
1440
|
}, [filtered, sortKey, sortDir]);
|
|
1441
|
+
const searchActive = filter.trim().length >= 3;
|
|
1442
|
+
const visibleItems = searchActive ? searchItems : filteredAndSorted;
|
|
1443
|
+
useEffect(() => {
|
|
1444
|
+
if (searchActive) {
|
|
1445
|
+
addDebugMessage(`[State] searchActive=${searchActive}, searchItems=${searchItems.length}, visibleItems=${visibleItems.length}, filter="${filter}"`);
|
|
1446
|
+
}
|
|
1447
|
+
}, [searchActive, searchItems.length, visibleItems.length, filter]);
|
|
1232
1448
|
useEffect(() => {
|
|
1233
|
-
setCursor((c) => Math.min(c, Math.max(0,
|
|
1234
|
-
}, [
|
|
1449
|
+
setCursor((c) => Math.min(c, Math.max(0, (searchActive ? searchItems.length : items.length) - 1)));
|
|
1450
|
+
}, [searchActive, searchItems.length, items.length]);
|
|
1235
1451
|
const headerHeight = 2;
|
|
1236
1452
|
const footerHeight = 4;
|
|
1237
1453
|
const containerPadding = 2;
|
|
@@ -1239,7 +1455,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1239
1455
|
const listHeight = Math.max(1, contentHeight - (filterMode ? 2 : 0) - 2);
|
|
1240
1456
|
const spacingLines = density;
|
|
1241
1457
|
const windowed = useMemo(() => {
|
|
1242
|
-
const total =
|
|
1458
|
+
const total = visibleItems.length;
|
|
1243
1459
|
const LINES_PER_REPO = 3 + spacingLines;
|
|
1244
1460
|
const visibleRepos = Math.max(1, Math.floor(listHeight / LINES_PER_REPO));
|
|
1245
1461
|
if (visibleRepos >= total) return { start: 0, end: total };
|
|
@@ -1249,7 +1465,15 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1249
1465
|
start = Math.min(start, Math.max(0, total - visibleRepos));
|
|
1250
1466
|
const end = Math.min(total, start + visibleRepos + buffer);
|
|
1251
1467
|
return { start, end };
|
|
1252
|
-
}, [
|
|
1468
|
+
}, [visibleItems.length, cursor, listHeight, spacingLines]);
|
|
1469
|
+
useEffect(() => {
|
|
1470
|
+
const nearEnd = cursor >= visibleItems.length - 5;
|
|
1471
|
+
if (searchActive) {
|
|
1472
|
+
if (!searchLoading && searchHasNextPage && nearEnd) fetchSearchPage(searchEndCursor);
|
|
1473
|
+
} else {
|
|
1474
|
+
if (!loading && !loadingMore && hasNextPage && nearEnd) fetchPage(endCursor);
|
|
1475
|
+
}
|
|
1476
|
+
}, [cursor, visibleItems.length, searchActive, searchLoading, searchHasNextPage, searchEndCursor, loading, loadingMore, hasNextPage, endCursor]);
|
|
1253
1477
|
function openInBrowser(url) {
|
|
1254
1478
|
const platform = process.platform;
|
|
1255
1479
|
const cmd = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
@@ -1262,12 +1486,12 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1262
1486
|
/* @__PURE__ */ jsx(Text, { bold: true, color: modalOpen ? "gray" : void 0, dimColor: modalOpen ? true : void 0, children: " Repositories" }),
|
|
1263
1487
|
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
1264
1488
|
"(",
|
|
1265
|
-
|
|
1489
|
+
visibleItems.length,
|
|
1266
1490
|
"/",
|
|
1267
|
-
totalCount,
|
|
1491
|
+
searchActive ? searchTotalCount : totalCount,
|
|
1268
1492
|
")"
|
|
1269
1493
|
] }),
|
|
1270
|
-
loading && /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, marginLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) })
|
|
1494
|
+
(loading || searchLoading) && /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, marginLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) })
|
|
1271
1495
|
] }),
|
|
1272
1496
|
rateLimit && /* @__PURE__ */ jsxs(Text, { color: lowRate ? "yellow" : "gray", children: [
|
|
1273
1497
|
"API: ",
|
|
@@ -1276,7 +1500,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1276
1500
|
rateLimit.limit,
|
|
1277
1501
|
prevRateLimit !== void 0 && prevRateLimit !== rateLimit.remaining && /* @__PURE__ */ jsx(Text, { color: rateLimit.remaining < prevRateLimit ? "red" : "green", children: ` (${rateLimit.remaining - prevRateLimit > 0 ? "+" : ""}${rateLimit.remaining - prevRateLimit})` })
|
|
1278
1502
|
] })
|
|
1279
|
-
] }), [
|
|
1503
|
+
] }), [visibleItems.length, searchActive, searchTotalCount, totalCount, loading, searchLoading, rateLimit, lowRate, modalOpen, prevRateLimit]);
|
|
1280
1504
|
if (error) {
|
|
1281
1505
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: [
|
|
1282
1506
|
/* @__PURE__ */ jsx(Text, { color: "red", children: error }),
|
|
@@ -1602,7 +1826,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1602
1826
|
" \u2022 Y to confirm \u2022 C to cancel"
|
|
1603
1827
|
] }) })
|
|
1604
1828
|
] }) }) : infoMode ? /* @__PURE__ */ jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: (() => {
|
|
1605
|
-
const repo =
|
|
1829
|
+
const repo = visibleItems[cursor];
|
|
1606
1830
|
if (!repo) return /* @__PURE__ */ jsx(Text, { color: "red", children: "No repository selected." });
|
|
1607
1831
|
const langName = repo.primaryLanguage?.name || "N/A";
|
|
1608
1832
|
const langColor = repo.primaryLanguage?.color || "#666666";
|
|
@@ -1648,10 +1872,21 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1648
1872
|
"Forks - Commits Behind: ",
|
|
1649
1873
|
forkTracking ? "ON" : "OFF"
|
|
1650
1874
|
] }),
|
|
1651
|
-
filter && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
1875
|
+
filter && !searchActive && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
1652
1876
|
'Filter: "',
|
|
1653
1877
|
filter,
|
|
1654
1878
|
'"'
|
|
1879
|
+
] }),
|
|
1880
|
+
searchActive && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1881
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
1882
|
+
'Search: "',
|
|
1883
|
+
filter.trim(),
|
|
1884
|
+
'"'
|
|
1885
|
+
] }),
|
|
1886
|
+
searchLoading && /* @__PURE__ */ jsx(Box, { marginLeft: 1, children: /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
1887
|
+
/* @__PURE__ */ jsx(SlowSpinner, {}),
|
|
1888
|
+
" Searching\u2026"
|
|
1889
|
+
] }) })
|
|
1655
1890
|
] })
|
|
1656
1891
|
] }),
|
|
1657
1892
|
filterMode && /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
@@ -1660,20 +1895,50 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1660
1895
|
TextInput,
|
|
1661
1896
|
{
|
|
1662
1897
|
value: filter,
|
|
1663
|
-
onChange:
|
|
1664
|
-
|
|
1665
|
-
|
|
1898
|
+
onChange: (val) => {
|
|
1899
|
+
addDebugMessage(`[onChange] val="${val}"`);
|
|
1900
|
+
setFilter(val);
|
|
1901
|
+
const q = (val || "").trim();
|
|
1902
|
+
addDebugMessage(`[onChange] trimmed="${q}", len=${q.length}`);
|
|
1903
|
+
if (q.length >= 3) {
|
|
1904
|
+
addDebugMessage(`[onChange] Triggering search for "${q}"`);
|
|
1905
|
+
let policy = "cache-first";
|
|
1906
|
+
try {
|
|
1907
|
+
const key = makeSearchKey({
|
|
1908
|
+
viewer: viewerLogin || "unknown",
|
|
1909
|
+
q,
|
|
1910
|
+
sortKey,
|
|
1911
|
+
sortDir,
|
|
1912
|
+
pageSize: PAGE_SIZE,
|
|
1913
|
+
forkTracking
|
|
1914
|
+
});
|
|
1915
|
+
policy = isFresh(key, 90 * 1e3) ? "cache-first" : "network-only";
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
addDebugMessage(`[onChange] Calling fetchSearchPage with q="${q}"`);
|
|
1919
|
+
fetchSearchPage(null, true, policy, q);
|
|
1920
|
+
} else {
|
|
1921
|
+
setSearchItems([]);
|
|
1922
|
+
setSearchEndCursor(null);
|
|
1923
|
+
setSearchHasNextPage(false);
|
|
1924
|
+
setSearchTotalCount(0);
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
onSubmit: () => {
|
|
1928
|
+
setFilterMode(false);
|
|
1929
|
+
},
|
|
1930
|
+
placeholder: "Type to search (3+ chars for server search)..."
|
|
1666
1931
|
}
|
|
1667
1932
|
)
|
|
1668
1933
|
] }),
|
|
1669
1934
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: listHeight, children: [
|
|
1670
|
-
|
|
1935
|
+
filterMode && filter.trim().length > 0 && filter.trim().length < 3 ? /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Type at least 3 characters to search" }) }) : visibleItems.slice(windowed.start, windowed.end).map((repo, i) => {
|
|
1671
1936
|
const idx = windowed.start + i;
|
|
1672
1937
|
return /* @__PURE__ */ jsx(
|
|
1673
1938
|
RepoRow,
|
|
1674
1939
|
{
|
|
1675
1940
|
repo,
|
|
1676
|
-
selected: idx === cursor,
|
|
1941
|
+
selected: filterMode && searchActive ? false : idx === cursor,
|
|
1677
1942
|
index: idx + 1,
|
|
1678
1943
|
maxWidth: terminalWidth - 6,
|
|
1679
1944
|
spacingLines,
|
|
@@ -1686,12 +1951,16 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin }) {
|
|
|
1686
1951
|
/* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) }),
|
|
1687
1952
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "Loading more repositories..." })
|
|
1688
1953
|
] }) }),
|
|
1689
|
-
!loading &&
|
|
1954
|
+
!loading && !searchLoading && visibleItems.length === 0 && /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: searchActive ? "No repositories match your search" : filter ? "No repositories match your filter" : "No repositories found" }) })
|
|
1690
1955
|
] })
|
|
1691
1956
|
] }) }),
|
|
1692
1957
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
|
|
1693
1958
|
/* @__PURE__ */ jsx(Box, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: modalOpen ? true : void 0, children: "\u2191\u2193 Navigate \u2022 Ctrl+G Top \u2022 G Bottom \u2022 / Filter \u2022 S Sort \u2022 D Direction \u2022 T Density \u2022 F Forks - Commits Behind \u2022 \u23CE/O Open" }) }),
|
|
1694
|
-
/* @__PURE__ */ jsx(Box, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: modalOpen ? true : void 0, children: "Del/Ctrl+Backspace Delete \u2022 Ctrl+A Un/Archive \u2022 Ctrl+U Sync Fork \u2022 I Info \u2022 Ctrl+L Logout \u2022 R Refresh \u2022 Q Quit" }) })
|
|
1959
|
+
/* @__PURE__ */ jsx(Box, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: modalOpen ? true : void 0, children: "Del/Ctrl+Backspace Delete \u2022 Ctrl+A Un/Archive \u2022 Ctrl+U Sync Fork \u2022 I Info \u2022 Ctrl+I Cache \u2022 Ctrl+L Logout \u2022 R Refresh \u2022 Q Quit" }) })
|
|
1960
|
+
] }),
|
|
1961
|
+
process.env.GH_MANAGER_DEBUG === "1" && /* @__PURE__ */ jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [
|
|
1962
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "Debug Messages:" }),
|
|
1963
|
+
debugMessages.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: "No debug messages yet..." }) : debugMessages.map((msg, i) => /* @__PURE__ */ jsx(Text, { color: "gray", children: msg }, i))
|
|
1695
1964
|
] })
|
|
1696
1965
|
] });
|
|
1697
1966
|
}
|
|
@@ -1858,7 +2127,8 @@ function App() {
|
|
|
1858
2127
|
/* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
1859
2128
|
"v",
|
|
1860
2129
|
packageJson.version
|
|
1861
|
-
] })
|
|
2130
|
+
] }),
|
|
2131
|
+
process.env.GH_MANAGER_DEBUG === "1" && /* @__PURE__ */ jsx2(Text2, { backgroundColor: "blue", color: "white", children: " debug mode " })
|
|
1862
2132
|
] }),
|
|
1863
2133
|
viewer && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
1864
2134
|
"@",
|
|
@@ -1982,6 +2252,9 @@ function App() {
|
|
|
1982
2252
|
|
|
1983
2253
|
// src/index.tsx
|
|
1984
2254
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2255
|
+
if (process.env.GH_MANAGER_DEBUG === "1") {
|
|
2256
|
+
process.stderr.write("\u{1F41B} Debug mode enabled\n");
|
|
2257
|
+
}
|
|
1985
2258
|
process.on("uncaughtException", (err) => {
|
|
1986
2259
|
console.error("Unhandled error:", err.message || err);
|
|
1987
2260
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gh-manager-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Interactive CLI to manage your GitHub repos (personal) with Ink",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,12 +38,13 @@
|
|
|
38
38
|
"node": ">=18"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@apollo/client": "^3.
|
|
41
|
+
"@apollo/client": "^3.14.0",
|
|
42
42
|
"@octokit/graphql": "^9.0.1",
|
|
43
43
|
"apollo3-cache-persist": "^0.14.1",
|
|
44
44
|
"chalk": "^5.6.0",
|
|
45
45
|
"dotenv": "^17.2.1",
|
|
46
46
|
"env-paths": "^3.0.0",
|
|
47
|
+
"graphql": "^16.11.0",
|
|
47
48
|
"ink": "^6.2.3",
|
|
48
49
|
"ink-spinner": "^5.0.0",
|
|
49
50
|
"ink-text-input": "^6.0.0",
|