github-search-cli 9.4.1 → 9.4.3
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 +65 -1
- package/README.md +268 -237
- package/bin/ghfind.js +98 -89
- package/completions/ghfind.bash +1 -1
- package/completions/ghfind.fish +3 -1
- package/completions/ghfind.zsh +6 -0
- package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
- package/dist/cli.js +1179 -403
- package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
- package/package.json +16 -5
- package/release-notes/9.3.0.md +32 -29
- package/scripts/build.ts +34 -19
- package/scripts/generate-svg-demo.js +218 -168
- package/scripts/postinstall.js +176 -176
- package/src/bookmarks.ts +69 -69
- package/src/cli.ts +650 -566
- package/src/compare.ts +69 -47
- package/src/config.ts +70 -65
- package/src/deepdive.ts +149 -118
- package/src/doctor.ts +287 -287
- package/src/error-report.ts +8 -12
- package/src/errors.ts +62 -59
- package/src/explore.ts +34 -29
- package/src/export.ts +73 -63
- package/src/help.ts +157 -157
- package/src/history.ts +69 -55
- package/src/init.ts +60 -55
- package/src/landing.ts +106 -104
- package/src/login.ts +186 -0
- package/src/markdown-render.ts +1 -6
- package/src/notifications.ts +75 -59
- package/src/open-url.ts +16 -16
- package/src/org.ts +299 -0
- package/src/output.ts +153 -137
- package/src/package-browser.ts +317 -305
- package/src/package.ts +149 -137
- package/src/pipe.ts +60 -54
- package/src/releases.ts +182 -144
- package/src/saved-searches.ts +72 -50
- package/src/search-state.ts +283 -229
- package/src/search.ts +678 -653
- package/src/session.ts +26 -24
- package/src/share.ts +61 -54
- package/src/status.ts +68 -61
- package/src/storage.ts +136 -106
- package/src/themes.ts +259 -116
- package/src/tips.ts +42 -42
- package/src/toast.ts +70 -69
- package/src/trending-parser.ts +104 -0
- package/src/trending.ts +481 -462
- package/src/tui.ts +3015 -2867
- package/src/types.ts +160 -158
- package/src/update-check.ts +231 -215
- package/src/version.ts +33 -0
- package/src/watch.ts +95 -74
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
loadConfig,
|
|
5
5
|
saveConfig,
|
|
6
6
|
stateDir
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GYIL4S5X.js";
|
|
8
8
|
|
|
9
9
|
// src/errors.ts
|
|
10
10
|
var SearchCliError = class extends Error {
|
|
@@ -56,6 +56,73 @@ var ParseError = class extends SearchCliError {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// src/trending-parser.ts
|
|
60
|
+
function flattenHtml(html) {
|
|
61
|
+
return html.replace(/>\s+</g, "><").replace(/\s+/g, " ");
|
|
62
|
+
}
|
|
63
|
+
function parseRepoLink(block) {
|
|
64
|
+
const anchor = block.match(
|
|
65
|
+
/<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([^\"?#]+)"[^>]*class="Link"/
|
|
66
|
+
);
|
|
67
|
+
if (!anchor) return void 0;
|
|
68
|
+
return { owner: anchor[1], name: anchor[2] };
|
|
69
|
+
}
|
|
70
|
+
function parseStars(block) {
|
|
71
|
+
const starsMatch = block.match(
|
|
72
|
+
/href="\/[^\/\"]+\/[^\/\"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/
|
|
73
|
+
);
|
|
74
|
+
return starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
|
|
75
|
+
}
|
|
76
|
+
function parseStarsToday(block) {
|
|
77
|
+
const growthMatch = block.match(/(\d[\d,]*)\s+stars\s+(today|this\s+\w+)/);
|
|
78
|
+
return growthMatch ? parseInt(growthMatch[1].replace(/,/g, ""), 10) : 0;
|
|
79
|
+
}
|
|
80
|
+
function parseLanguage(block) {
|
|
81
|
+
const langMatch = block.match(/itemprop="programmingLanguage">([^<]+)</);
|
|
82
|
+
return langMatch ? langMatch[1].trim() : "";
|
|
83
|
+
}
|
|
84
|
+
function parseDescription(block) {
|
|
85
|
+
const descMatch = block.match(
|
|
86
|
+
/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([^<]+)</
|
|
87
|
+
);
|
|
88
|
+
return descMatch ? descMatch[1].trim() : "";
|
|
89
|
+
}
|
|
90
|
+
function parseTrendingHtml(html) {
|
|
91
|
+
const flat = flattenHtml(html);
|
|
92
|
+
const repos = [];
|
|
93
|
+
const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
|
|
94
|
+
let match;
|
|
95
|
+
let rank = 0;
|
|
96
|
+
while ((match = articleRe.exec(flat)) !== null) {
|
|
97
|
+
rank++;
|
|
98
|
+
const block = match[1];
|
|
99
|
+
const link = parseRepoLink(block);
|
|
100
|
+
if (!link) continue;
|
|
101
|
+
repos.push({
|
|
102
|
+
rank,
|
|
103
|
+
owner: link.owner,
|
|
104
|
+
name: link.name,
|
|
105
|
+
stars: parseStars(block),
|
|
106
|
+
starsToday: parseStarsToday(block),
|
|
107
|
+
language: parseLanguage(block),
|
|
108
|
+
description: parseDescription(block)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (rank === 0) {
|
|
112
|
+
throw new ParseError(
|
|
113
|
+
"github.com/trending",
|
|
114
|
+
"no article rows found \u2014 response may be an error page or a rewrite"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (repos.length === 0) {
|
|
118
|
+
throw new ParseError(
|
|
119
|
+
"github.com/trending",
|
|
120
|
+
"article rows present but no repository heading could be parsed"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return repos;
|
|
124
|
+
}
|
|
125
|
+
|
|
59
126
|
// src/search.ts
|
|
60
127
|
var noopLogger = {
|
|
61
128
|
debug: () => {
|
|
@@ -235,15 +302,28 @@ function matchQualifier(token) {
|
|
|
235
302
|
}
|
|
236
303
|
function applyFlagFilters(query, flags) {
|
|
237
304
|
const qualifiers = [...query.qualifiers];
|
|
238
|
-
if (flags.language)
|
|
239
|
-
|
|
240
|
-
if (flags.
|
|
241
|
-
|
|
242
|
-
if (flags.
|
|
305
|
+
if (flags.language)
|
|
306
|
+
qualifiers.push({ key: "language", value: flags.language, negated: false });
|
|
307
|
+
if (flags.stars)
|
|
308
|
+
qualifiers.push({ key: "stars", value: flags.stars, negated: false });
|
|
309
|
+
if (flags.org)
|
|
310
|
+
qualifiers.push({ key: "org", value: flags.org, negated: false });
|
|
311
|
+
if (flags.user)
|
|
312
|
+
qualifiers.push({ key: "user", value: flags.user, negated: false });
|
|
313
|
+
if (flags.topic)
|
|
314
|
+
qualifiers.push({ key: "topic", value: flags.topic, negated: false });
|
|
243
315
|
if (flags.archived !== void 0)
|
|
244
|
-
qualifiers.push({
|
|
316
|
+
qualifiers.push({
|
|
317
|
+
key: "archived",
|
|
318
|
+
value: flags.archived ? "true" : "false",
|
|
319
|
+
negated: false
|
|
320
|
+
});
|
|
245
321
|
if (flags.fork !== void 0)
|
|
246
|
-
qualifiers.push({
|
|
322
|
+
qualifiers.push({
|
|
323
|
+
key: "fork",
|
|
324
|
+
value: flags.fork ? "true" : "false",
|
|
325
|
+
negated: false
|
|
326
|
+
});
|
|
247
327
|
return { ...query, qualifiers };
|
|
248
328
|
}
|
|
249
329
|
function validateQuery(query) {
|
|
@@ -359,12 +439,16 @@ var GitHubSearchAdapter = class {
|
|
|
359
439
|
throw new RateLimitError(!!token);
|
|
360
440
|
}
|
|
361
441
|
const body = await res.text().catch(() => "");
|
|
362
|
-
this.logger.error(
|
|
442
|
+
this.logger.error(
|
|
443
|
+
`[github] non-rate-limit 403: ${body.slice(0, 200)}`
|
|
444
|
+
);
|
|
363
445
|
throw new NetworkError();
|
|
364
446
|
}
|
|
365
447
|
if (!res.ok) {
|
|
366
448
|
const body = await res.text().catch(() => "");
|
|
367
|
-
this.logger.error(
|
|
449
|
+
this.logger.error(
|
|
450
|
+
`[github] API error ${res.status}: ${body.slice(0, 200)}`
|
|
451
|
+
);
|
|
368
452
|
throw new NetworkError();
|
|
369
453
|
}
|
|
370
454
|
const raw = await res.text();
|
|
@@ -374,11 +458,19 @@ var GitHubSearchAdapter = class {
|
|
|
374
458
|
} catch {
|
|
375
459
|
const snippet = raw.slice(0, 200);
|
|
376
460
|
this.logger.error(`[github] invalid JSON response: ${snippet}`);
|
|
377
|
-
throw new ParseError(
|
|
461
|
+
throw new ParseError(
|
|
462
|
+
"GitHub API",
|
|
463
|
+
snippet.includes("<!DOCTYPE") ? "GitHub returned an HTML page (maintenance or CAPTCHA?)" : "Invalid JSON response"
|
|
464
|
+
);
|
|
378
465
|
}
|
|
379
466
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.items)) {
|
|
380
|
-
this.logger.error(
|
|
381
|
-
|
|
467
|
+
this.logger.error(
|
|
468
|
+
`[github] unexpected response shape: ${JSON.stringify(parsed).slice(0, 200)}`
|
|
469
|
+
);
|
|
470
|
+
throw new ParseError(
|
|
471
|
+
"GitHub API",
|
|
472
|
+
"Unexpected response shape \u2014 missing items array"
|
|
473
|
+
);
|
|
382
474
|
}
|
|
383
475
|
const env = parsed;
|
|
384
476
|
totalCount = env.total_count;
|
|
@@ -409,31 +501,6 @@ function appendPage(url, page, perPage) {
|
|
|
409
501
|
return u.toString();
|
|
410
502
|
}
|
|
411
503
|
var TRENDING_URL = "https://github.com/trending";
|
|
412
|
-
function parseTrendingHtml(html) {
|
|
413
|
-
const flat = html.replace(/>\s+</g, "><").replace(/\s+/g, " ");
|
|
414
|
-
const repos = [];
|
|
415
|
-
const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
|
|
416
|
-
let match;
|
|
417
|
-
let rank = 0;
|
|
418
|
-
while ((match = articleRe.exec(flat)) !== null) {
|
|
419
|
-
rank++;
|
|
420
|
-
const block = match[1];
|
|
421
|
-
const repoMatch = block.match(/href="\/([^\/\"]+)\/([^\"?#]+)"[^>]*class="Link"/);
|
|
422
|
-
if (!repoMatch) continue;
|
|
423
|
-
const owner = repoMatch[1];
|
|
424
|
-
const name = repoMatch[2];
|
|
425
|
-
const starsMatch = block.match(/href="\/[^\/\"]+\/[^\/\"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/);
|
|
426
|
-
const stars = starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
|
|
427
|
-
const growthMatch = block.match(/(\d[\d,]*)\s+stars\s+(today|this\s+\w+)/);
|
|
428
|
-
const starsToday = growthMatch ? parseInt(growthMatch[1].replace(/,/g, ""), 10) : 0;
|
|
429
|
-
const langMatch = block.match(/itemprop="programmingLanguage">([^<]+)</);
|
|
430
|
-
const language = langMatch ? langMatch[1].trim() : "";
|
|
431
|
-
const descMatch = block.match(/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([^<]+)</);
|
|
432
|
-
const description = descMatch ? descMatch[1].trim() : "";
|
|
433
|
-
repos.push({ rank, owner, name, stars, starsToday, language, description });
|
|
434
|
-
}
|
|
435
|
-
return repos;
|
|
436
|
-
}
|
|
437
504
|
function trendingRepoToRepo(r) {
|
|
438
505
|
return {
|
|
439
506
|
id: 0,
|
|
@@ -495,10 +562,10 @@ var SearchModule = class _SearchModule {
|
|
|
495
562
|
String(options.page ?? 1),
|
|
496
563
|
options.trendingSince ?? "none"
|
|
497
564
|
);
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
565
|
+
const cached2 = _SearchModule.cache.get(cacheKey);
|
|
566
|
+
if (cached2) {
|
|
500
567
|
this.logger.debug("[search] cache hit");
|
|
501
|
-
return
|
|
568
|
+
return cached2;
|
|
502
569
|
}
|
|
503
570
|
this.logger.debug("[search] cache miss");
|
|
504
571
|
const response = await this.adapter.search(query, options);
|
|
@@ -622,6 +689,161 @@ function openUrl(url) {
|
|
|
622
689
|
}
|
|
623
690
|
}
|
|
624
691
|
|
|
692
|
+
// src/themes.ts
|
|
693
|
+
var THEMES = {
|
|
694
|
+
"tokyo-night": {
|
|
695
|
+
name: "tokyo-night",
|
|
696
|
+
// Genuine Tokyo Night palette — cool blue-violet mood
|
|
697
|
+
bg: "#1a1b26",
|
|
698
|
+
surface: "#24283b",
|
|
699
|
+
surfaceAlt: "#292e42",
|
|
700
|
+
text: "#c0caf5",
|
|
701
|
+
muted: "#565f89",
|
|
702
|
+
blue: "#7aa2f7",
|
|
703
|
+
green: "#9ece6a",
|
|
704
|
+
yellow: "#e0af68",
|
|
705
|
+
red: "#f7768e",
|
|
706
|
+
teal: "#73daca",
|
|
707
|
+
purple: "#bb9af7",
|
|
708
|
+
orange: "#ff9e64",
|
|
709
|
+
border: "#24283b",
|
|
710
|
+
borderAlt: "#1f2335",
|
|
711
|
+
separator: "#1f2335",
|
|
712
|
+
selectionBg: "#2d3f76",
|
|
713
|
+
selectionText: "#c0caf5",
|
|
714
|
+
success: "#9ece6a",
|
|
715
|
+
warning: "#e0af68",
|
|
716
|
+
error: "#f7768e",
|
|
717
|
+
surfaceDim: "#16161e",
|
|
718
|
+
accent: "#7aa2f7",
|
|
719
|
+
accentDim: "#5d7abf",
|
|
720
|
+
borderAccent: "#1f2335"
|
|
721
|
+
},
|
|
722
|
+
"premium-dark": {
|
|
723
|
+
name: "premium-dark",
|
|
724
|
+
// Warm counter-mood to Tokyo Night — amber/rust accent
|
|
725
|
+
bg: "#1e1e2e",
|
|
726
|
+
surface: "#262637",
|
|
727
|
+
surfaceAlt: "#2e2e42",
|
|
728
|
+
text: "#cdd6f4",
|
|
729
|
+
muted: "#6c7086",
|
|
730
|
+
blue: "#89b4fa",
|
|
731
|
+
green: "#a6e3a1",
|
|
732
|
+
yellow: "#f9e2af",
|
|
733
|
+
red: "#f38ba8",
|
|
734
|
+
teal: "#94e2d5",
|
|
735
|
+
purple: "#cba6f7",
|
|
736
|
+
orange: "#fab387",
|
|
737
|
+
border: "#313244",
|
|
738
|
+
borderAlt: "#2a2a3d",
|
|
739
|
+
separator: "#2a2a3d",
|
|
740
|
+
selectionBg: "#45475a",
|
|
741
|
+
selectionText: "#cdd6f4",
|
|
742
|
+
success: "#a6e3a1",
|
|
743
|
+
warning: "#f9e2af",
|
|
744
|
+
error: "#f38ba8",
|
|
745
|
+
surfaceDim: "#181825",
|
|
746
|
+
accent: "#f9e2af",
|
|
747
|
+
accentDim: "#c9a87a",
|
|
748
|
+
borderAccent: "#313244"
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function loadTheme(name) {
|
|
752
|
+
return THEMES[name] ?? THEMES["tokyo-night"];
|
|
753
|
+
}
|
|
754
|
+
function listThemes() {
|
|
755
|
+
return Object.keys(THEMES);
|
|
756
|
+
}
|
|
757
|
+
function parseHex(hex) {
|
|
758
|
+
let h = hex.trim().replace(/^#/, "");
|
|
759
|
+
if (h.length === 3)
|
|
760
|
+
h = h.split("").map((c) => c + c).join("");
|
|
761
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
762
|
+
return [
|
|
763
|
+
parseInt(h.slice(0, 2), 16),
|
|
764
|
+
parseInt(h.slice(2, 4), 16),
|
|
765
|
+
parseInt(h.slice(4, 6), 16)
|
|
766
|
+
];
|
|
767
|
+
}
|
|
768
|
+
var toHex2 = (n) => n.toString(16).padStart(2, "0");
|
|
769
|
+
function mixHex(a, b, t3) {
|
|
770
|
+
const ca = parseHex(a);
|
|
771
|
+
const cb = parseHex(b);
|
|
772
|
+
if (!ca || !cb) return a;
|
|
773
|
+
const mix = (x, y) => Math.round(x + (y - x) * t3);
|
|
774
|
+
return `#${toHex2(mix(ca[0], cb[0]))}${toHex2(mix(ca[1], cb[1]))}${toHex2(mix(ca[2], cb[2]))}`;
|
|
775
|
+
}
|
|
776
|
+
function isDarkHex(hex) {
|
|
777
|
+
const c = parseHex(hex);
|
|
778
|
+
if (!c) return false;
|
|
779
|
+
return (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255 < 0.5;
|
|
780
|
+
}
|
|
781
|
+
function deriveSurfaceLayers(bgHex) {
|
|
782
|
+
if (!isDarkHex(bgHex)) return null;
|
|
783
|
+
const w = "#ffffff";
|
|
784
|
+
return {
|
|
785
|
+
surface: mixHex(bgHex, w, 0.045),
|
|
786
|
+
surfaceAlt: mixHex(bgHex, w, 0.07),
|
|
787
|
+
// Sunken modal/overlay panels (toast etc.) — darker than the backdrop
|
|
788
|
+
surfaceDim: mixHex(bgHex, "#000000", 0.15),
|
|
789
|
+
border: mixHex(bgHex, w, 0.09),
|
|
790
|
+
borderAlt: mixHex(bgHex, w, 0.055),
|
|
791
|
+
separator: mixHex(bgHex, w, 0.055),
|
|
792
|
+
borderAccent: mixHex(bgHex, w, 0.065)
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function parseOscColor(spec) {
|
|
796
|
+
const m = spec.trim().match(
|
|
797
|
+
/^(?:rgb|rgba):([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})(?:\/[0-9a-fA-F]{1,4})?$/
|
|
798
|
+
);
|
|
799
|
+
if (!m) return null;
|
|
800
|
+
const chan = (h) => {
|
|
801
|
+
const value = parseInt(h, 16);
|
|
802
|
+
const max = 16 ** h.length - 1;
|
|
803
|
+
return Math.round(value * 255 / max);
|
|
804
|
+
};
|
|
805
|
+
const hex = (n) => n.toString(16).padStart(2, "0");
|
|
806
|
+
return `#${hex(chan(m[1]))}${hex(chan(m[2]))}${hex(chan(m[3]))}`;
|
|
807
|
+
}
|
|
808
|
+
async function detectTerminalBackground(timeoutMs = 400) {
|
|
809
|
+
const stdin = process.stdin;
|
|
810
|
+
const stdout = process.stdout;
|
|
811
|
+
const setRawMode = stdin.setRawMode;
|
|
812
|
+
if (!stdout.isTTY || !stdin.isTTY || typeof setRawMode !== "function")
|
|
813
|
+
return null;
|
|
814
|
+
return new Promise((resolve) => {
|
|
815
|
+
const wasRaw = stdin.isRaw === true;
|
|
816
|
+
let settled = false;
|
|
817
|
+
let buffer = "";
|
|
818
|
+
const finish = (result) => {
|
|
819
|
+
if (settled) return;
|
|
820
|
+
settled = true;
|
|
821
|
+
clearTimeout(timer);
|
|
822
|
+
stdin.removeListener("data", onData);
|
|
823
|
+
try {
|
|
824
|
+
setRawMode.call(stdin, wasRaw);
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
stdin.pause();
|
|
828
|
+
resolve(result);
|
|
829
|
+
};
|
|
830
|
+
const onData = (d) => {
|
|
831
|
+
buffer += d.toString("latin1");
|
|
832
|
+
const m = buffer.match(/\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)/);
|
|
833
|
+
if (m) finish(parseOscColor(m[1]));
|
|
834
|
+
};
|
|
835
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
836
|
+
try {
|
|
837
|
+
setRawMode.call(stdin, true);
|
|
838
|
+
stdin.on("data", onData);
|
|
839
|
+
stdin.resume();
|
|
840
|
+
stdout.write("\x1B]11;?\x07");
|
|
841
|
+
} catch {
|
|
842
|
+
finish(null);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
625
847
|
// src/package-browser.ts
|
|
626
848
|
var C = {
|
|
627
849
|
bg: "#1a1b26",
|
|
@@ -660,6 +882,11 @@ function formatPackageLine(pack) {
|
|
|
660
882
|
return new StyledText(chunks);
|
|
661
883
|
}
|
|
662
884
|
async function launchPackageBrowser() {
|
|
885
|
+
const terminalBg = await detectTerminalBackground();
|
|
886
|
+
if (terminalBg) {
|
|
887
|
+
C.bg = terminalBg;
|
|
888
|
+
Object.assign(C, deriveSurfaceLayers(terminalBg));
|
|
889
|
+
}
|
|
663
890
|
let renderer;
|
|
664
891
|
try {
|
|
665
892
|
renderer = await createCliRenderer();
|
|
@@ -801,7 +1028,9 @@ async function launchPackageBrowser() {
|
|
|
801
1028
|
isLoading = true;
|
|
802
1029
|
dirty = false;
|
|
803
1030
|
clearRows();
|
|
804
|
-
setStatus({
|
|
1031
|
+
setStatus({
|
|
1032
|
+
content: t`${dim(fg(C.muted)(` Loading packages for "${q}"...`))}`
|
|
1033
|
+
});
|
|
805
1034
|
renderer.requestRender();
|
|
806
1035
|
try {
|
|
807
1036
|
const searcher = createPackageSearch();
|
|
@@ -1131,22 +1360,42 @@ async function runDoctor() {
|
|
|
1131
1360
|
}
|
|
1132
1361
|
|
|
1133
1362
|
// src/error-report.ts
|
|
1134
|
-
import {
|
|
1363
|
+
import {
|
|
1364
|
+
platform as osPlatform,
|
|
1365
|
+
arch as osArch,
|
|
1366
|
+
version as nodeVersion,
|
|
1367
|
+
versions as nodeVersions
|
|
1368
|
+
} from "process";
|
|
1135
1369
|
import { spawn } from "child_process";
|
|
1370
|
+
|
|
1371
|
+
// src/version.ts
|
|
1136
1372
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1137
1373
|
import { join as join2, dirname as dirname2 } from "path";
|
|
1138
1374
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1139
|
-
var
|
|
1140
|
-
|
|
1141
|
-
|
|
1375
|
+
var cached = null;
|
|
1376
|
+
function getVersion() {
|
|
1377
|
+
if (cached) return cached;
|
|
1378
|
+
if (true) {
|
|
1379
|
+
cached = "9.4.3";
|
|
1380
|
+
return cached;
|
|
1381
|
+
}
|
|
1142
1382
|
try {
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1146
|
-
|
|
1383
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
1384
|
+
const pkg = JSON.parse(
|
|
1385
|
+
readFileSync2(join2(here, "..", "package.json"), "utf-8")
|
|
1386
|
+
);
|
|
1387
|
+
cached = pkg.version ?? "unknown";
|
|
1147
1388
|
} catch {
|
|
1148
|
-
|
|
1389
|
+
cached = "unknown";
|
|
1149
1390
|
}
|
|
1391
|
+
return cached;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/error-report.ts
|
|
1395
|
+
var REPO = "codersdfs/search-cli";
|
|
1396
|
+
var NEW_ISSUE_URL = `https://github.com/${REPO}/issues/new`;
|
|
1397
|
+
function getPackageVersion() {
|
|
1398
|
+
return getVersion();
|
|
1150
1399
|
}
|
|
1151
1400
|
function runtimeName() {
|
|
1152
1401
|
const bunVer = nodeVersions.bun;
|
|
@@ -1282,7 +1531,9 @@ function formatMarkdown(repos) {
|
|
|
1282
1531
|
return [header, sep, ...rows].join("\n");
|
|
1283
1532
|
}
|
|
1284
1533
|
function formatText(repos) {
|
|
1285
|
-
return repos.map(
|
|
1534
|
+
return repos.map(
|
|
1535
|
+
(r, i) => `${i + 1}. ${r.fullName} \u2605 ${r.stars.toLocaleString()} ${r.url}`
|
|
1536
|
+
).join("\n");
|
|
1286
1537
|
}
|
|
1287
1538
|
function formatUrls(repos) {
|
|
1288
1539
|
return repos.map((r) => r.url).join("\n");
|
|
@@ -1339,12 +1590,21 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1339
1590
|
const trendingSearch = createTrendingSearch();
|
|
1340
1591
|
const response = await trendingSearch.search(
|
|
1341
1592
|
{ keywords: [], qualifiers: [], raw: "trending" },
|
|
1342
|
-
{
|
|
1593
|
+
{
|
|
1594
|
+
limit: opts.limit,
|
|
1595
|
+
sort: opts.sort,
|
|
1596
|
+
json: false,
|
|
1597
|
+
verbose: false,
|
|
1598
|
+
trendingSince: since
|
|
1599
|
+
}
|
|
1343
1600
|
);
|
|
1344
1601
|
repos = response.repos;
|
|
1345
1602
|
} else {
|
|
1346
1603
|
const parsed = applyFlagFilters(parseQuery(opts.query), {});
|
|
1347
|
-
const provider = createGitHubSearch(
|
|
1604
|
+
const provider = createGitHubSearch(
|
|
1605
|
+
void 0,
|
|
1606
|
+
opts.token ? [opts.token] : []
|
|
1607
|
+
);
|
|
1348
1608
|
const options = {
|
|
1349
1609
|
limit: opts.limit,
|
|
1350
1610
|
sort: opts.sort,
|
|
@@ -1363,7 +1623,9 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1363
1623
|
failCount++;
|
|
1364
1624
|
onError(err instanceof Error ? err : new Error(String(err)));
|
|
1365
1625
|
if (failCount >= MAX_FAILS) {
|
|
1366
|
-
onError(
|
|
1626
|
+
onError(
|
|
1627
|
+
new Error(`Watch stopped after ${MAX_FAILS} consecutive failures`)
|
|
1628
|
+
);
|
|
1367
1629
|
if (intervalId) clearInterval(intervalId);
|
|
1368
1630
|
}
|
|
1369
1631
|
}
|
|
@@ -1374,8 +1636,206 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1374
1636
|
}
|
|
1375
1637
|
}
|
|
1376
1638
|
|
|
1639
|
+
// src/org.ts
|
|
1640
|
+
function apiHeaders(token) {
|
|
1641
|
+
return {
|
|
1642
|
+
Accept: "application/vnd.github+json",
|
|
1643
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1644
|
+
"User-Agent": "ghfind",
|
|
1645
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
function normalizeOrgRepo(item) {
|
|
1649
|
+
return {
|
|
1650
|
+
fullName: item.full_name ?? "",
|
|
1651
|
+
url: item.html_url ?? "",
|
|
1652
|
+
description: item.description ?? null,
|
|
1653
|
+
language: item.language ?? null,
|
|
1654
|
+
stars: item.stargazers_count ?? 0,
|
|
1655
|
+
forks: item.forks_count ?? 0,
|
|
1656
|
+
pushedAt: item.pushed_at ?? ""
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function normalizeOrg(payload, repos) {
|
|
1660
|
+
const orgRepos = (repos ?? []).map(normalizeOrgRepo);
|
|
1661
|
+
const langCounts = /* @__PURE__ */ new Map();
|
|
1662
|
+
for (const r of orgRepos) {
|
|
1663
|
+
if (!r.language) continue;
|
|
1664
|
+
langCounts.set(r.language, (langCounts.get(r.language) ?? 0) + 1);
|
|
1665
|
+
}
|
|
1666
|
+
const topLanguages = [...langCounts.entries()].map(([language, count]) => ({ language, repos: count })).sort((a, b) => b.repos - a.repos || a.language.localeCompare(b.language)).slice(0, 5);
|
|
1667
|
+
const byStars = [...orgRepos].sort(
|
|
1668
|
+
(a, b) => b.stars - a.stars || a.fullName.localeCompare(b.fullName)
|
|
1669
|
+
);
|
|
1670
|
+
const byPushed = [...orgRepos].filter((r) => r.pushedAt).sort((a, b) => Date.parse(b.pushedAt) - Date.parse(a.pushedAt));
|
|
1671
|
+
return {
|
|
1672
|
+
login: payload.login ?? "",
|
|
1673
|
+
name: payload.name ?? null,
|
|
1674
|
+
description: payload.description ?? null,
|
|
1675
|
+
url: payload.html_url ?? "",
|
|
1676
|
+
blog: payload.blog ?? null,
|
|
1677
|
+
location: payload.location ?? null,
|
|
1678
|
+
createdAt: payload.created_at ?? "",
|
|
1679
|
+
publicRepoCount: payload.public_repos ?? 0,
|
|
1680
|
+
fetchedRepoCount: orgRepos.length,
|
|
1681
|
+
totalStars: orgRepos.reduce((sum, r) => sum + r.stars, 0),
|
|
1682
|
+
totalForks: orgRepos.reduce((sum, r) => sum + r.forks, 0),
|
|
1683
|
+
topLanguages,
|
|
1684
|
+
topRepos: byStars.slice(0, 5),
|
|
1685
|
+
activeRepos: byPushed.slice(0, 5)
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
async function fetchOrgProfile(org, options = {}) {
|
|
1689
|
+
const name = org.trim().replace(/^@/, "");
|
|
1690
|
+
if (!name) throw new Error("Usage: ghfind org <org-name>");
|
|
1691
|
+
const token = options.token;
|
|
1692
|
+
const limit = Math.min(Math.max(options.limit ?? 100, 1), 500);
|
|
1693
|
+
let orgPayload;
|
|
1694
|
+
try {
|
|
1695
|
+
const res = await fetch(
|
|
1696
|
+
`https://api.github.com/orgs/${encodeURIComponent(name)}`,
|
|
1697
|
+
{
|
|
1698
|
+
headers: apiHeaders(token)
|
|
1699
|
+
}
|
|
1700
|
+
);
|
|
1701
|
+
if (res.status === 404) {
|
|
1702
|
+
throw new Error(`Organization "${name}" not found.`);
|
|
1703
|
+
}
|
|
1704
|
+
if (!res.ok) {
|
|
1705
|
+
const body = await res.text().catch(() => "");
|
|
1706
|
+
throw new Error(`GitHub API error ${res.status}: ${body.slice(0, 200)}`);
|
|
1707
|
+
}
|
|
1708
|
+
orgPayload = await res.json();
|
|
1709
|
+
} catch (err) {
|
|
1710
|
+
if (err instanceof Error && err.message.includes(`Organization "${name}"`))
|
|
1711
|
+
throw err;
|
|
1712
|
+
throw new Error(
|
|
1713
|
+
`Failed to fetch org "${name}": ${err instanceof Error ? err.message : String(err)}`
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
const repos = [];
|
|
1717
|
+
if (orgPayload.public_repos > 0) {
|
|
1718
|
+
const perPage = Math.min(limit, 100);
|
|
1719
|
+
const maxPages = Math.ceil(limit / perPage);
|
|
1720
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
1721
|
+
const url = `https://api.github.com/orgs/${encodeURIComponent(name)}/repos?per_page=${perPage}&sort=pushed&page=${page}`;
|
|
1722
|
+
const res = await fetch(url, { headers: apiHeaders(token) });
|
|
1723
|
+
if (!res.ok) {
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
const items = await res.json();
|
|
1727
|
+
if (!Array.isArray(items)) break;
|
|
1728
|
+
repos.push(...items);
|
|
1729
|
+
if (repos.length >= limit || items.length < perPage) break;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return normalizeOrg(orgPayload, repos.slice(0, limit));
|
|
1733
|
+
}
|
|
1734
|
+
function formatOrgText(p) {
|
|
1735
|
+
const lines = [];
|
|
1736
|
+
const title = p.name ? `${p.login} \u2014 ${p.name}` : p.login;
|
|
1737
|
+
lines.push(title);
|
|
1738
|
+
if (p.description) lines.push(p.description);
|
|
1739
|
+
const meta = [];
|
|
1740
|
+
if (p.location) meta.push(`Location ${p.location}`);
|
|
1741
|
+
if (p.blog) meta.push(`Web ${p.blog}`);
|
|
1742
|
+
if (p.createdAt) meta.push(`Created ${p.createdAt.slice(0, 10)}`);
|
|
1743
|
+
if (meta.length > 0) lines.push(meta.join("\n"));
|
|
1744
|
+
lines.push(
|
|
1745
|
+
`Repos ${p.publicRepoCount.toLocaleString()} public${p.fetchedRepoCount < p.publicRepoCount ? ` (aggregated ${p.fetchedRepoCount})` : ""}`
|
|
1746
|
+
);
|
|
1747
|
+
lines.push(
|
|
1748
|
+
`Stars ${p.totalStars.toLocaleString()} (sum of aggregated repos)`
|
|
1749
|
+
);
|
|
1750
|
+
lines.push(
|
|
1751
|
+
`Forks ${p.totalForks.toLocaleString()} (sum of aggregated repos)`
|
|
1752
|
+
);
|
|
1753
|
+
lines.push(`URL ${p.url}`);
|
|
1754
|
+
if (p.topLanguages.length > 0) {
|
|
1755
|
+
const langs = p.topLanguages.map((l) => `${l.language} (${l.repos})`).join(", ");
|
|
1756
|
+
lines.push(`Languages ${langs}`);
|
|
1757
|
+
}
|
|
1758
|
+
if (p.topRepos.length > 0) {
|
|
1759
|
+
lines.push("");
|
|
1760
|
+
lines.push("Top repos by stars:");
|
|
1761
|
+
for (const r of p.topRepos) {
|
|
1762
|
+
lines.push(
|
|
1763
|
+
` ${r.fullName} \u2605 ${r.stars.toLocaleString()} ${r.language ?? ""}`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
if (p.activeRepos.length > 0) {
|
|
1768
|
+
lines.push("");
|
|
1769
|
+
lines.push("Recently active:");
|
|
1770
|
+
for (const r of p.activeRepos) {
|
|
1771
|
+
lines.push(` ${r.fullName} pushed ${r.pushedAt.slice(0, 10)}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return lines.join("\n");
|
|
1775
|
+
}
|
|
1776
|
+
function formatOrgJson(p) {
|
|
1777
|
+
return JSON.stringify(p, null, 2);
|
|
1778
|
+
}
|
|
1779
|
+
function formatOrgCsv(p) {
|
|
1780
|
+
const esc = (v) => /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v;
|
|
1781
|
+
const row = [
|
|
1782
|
+
p.login,
|
|
1783
|
+
p.name ?? "",
|
|
1784
|
+
p.url,
|
|
1785
|
+
String(p.publicRepoCount),
|
|
1786
|
+
String(p.totalStars),
|
|
1787
|
+
String(p.totalForks),
|
|
1788
|
+
p.topLanguages.map((l) => l.language).join(";"),
|
|
1789
|
+
p.createdAt
|
|
1790
|
+
].map(esc);
|
|
1791
|
+
return [
|
|
1792
|
+
"login,name,url,public_repos,total_stars,total_forks,top_languages,created_at",
|
|
1793
|
+
row.join(",")
|
|
1794
|
+
].join("\n");
|
|
1795
|
+
}
|
|
1796
|
+
function formatOrgMarkdown(p) {
|
|
1797
|
+
const lines = [];
|
|
1798
|
+
lines.push(`# ${p.login}${p.name ? ` \u2014 ${p.name}` : ""}`);
|
|
1799
|
+
if (p.description) lines.push("", p.description);
|
|
1800
|
+
lines.push(
|
|
1801
|
+
"",
|
|
1802
|
+
"| Metric | Value |",
|
|
1803
|
+
"| --- | --- |",
|
|
1804
|
+
`| Public repos | ${p.publicRepoCount} |`,
|
|
1805
|
+
`| Total stars (aggregated) | ${p.totalStars.toLocaleString()} |`,
|
|
1806
|
+
`| Total forks (aggregated) | ${p.totalForks.toLocaleString()} |`
|
|
1807
|
+
);
|
|
1808
|
+
if (p.topLanguages.length > 0) {
|
|
1809
|
+
lines.push(
|
|
1810
|
+
`| Top languages | ${p.topLanguages.map((l) => `${l.language} (${l.repos})`).join(", ")} |`
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
if (p.topRepos.length > 0) {
|
|
1814
|
+
lines.push(
|
|
1815
|
+
"",
|
|
1816
|
+
"## Top repos by stars",
|
|
1817
|
+
"",
|
|
1818
|
+
"| Repo | Stars | Language |",
|
|
1819
|
+
"| --- | --- | --- |"
|
|
1820
|
+
);
|
|
1821
|
+
for (const r of p.topRepos) {
|
|
1822
|
+
lines.push(
|
|
1823
|
+
`| [${r.fullName}](${r.url}) | ${r.stars.toLocaleString()} | ${r.language ?? "\u2014"} |`
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return lines.join("\n");
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1377
1830
|
// src/storage.ts
|
|
1378
|
-
import {
|
|
1831
|
+
import {
|
|
1832
|
+
existsSync as existsSync2,
|
|
1833
|
+
mkdirSync as mkdirSync2,
|
|
1834
|
+
readFileSync as readFileSync3,
|
|
1835
|
+
writeFileSync as writeFileSync2,
|
|
1836
|
+
appendFileSync,
|
|
1837
|
+
unlinkSync
|
|
1838
|
+
} from "fs";
|
|
1379
1839
|
import { join as join3 } from "path";
|
|
1380
1840
|
function ensureDir() {
|
|
1381
1841
|
mkdirSync2(stateDir(), { recursive: true });
|
|
@@ -1395,7 +1855,11 @@ function writeJSON(filename, data) {
|
|
|
1395
1855
|
function appendJSONL(filename, obj) {
|
|
1396
1856
|
ensureDir();
|
|
1397
1857
|
try {
|
|
1398
|
-
appendFileSync(
|
|
1858
|
+
appendFileSync(
|
|
1859
|
+
join3(stateDir(), filename),
|
|
1860
|
+
JSON.stringify(obj) + "\n",
|
|
1861
|
+
"utf-8"
|
|
1862
|
+
);
|
|
1399
1863
|
} catch {
|
|
1400
1864
|
}
|
|
1401
1865
|
}
|
|
@@ -1416,8 +1880,12 @@ function writeRaw(filename, data) {
|
|
|
1416
1880
|
function debugLog(msg) {
|
|
1417
1881
|
if (!process.env.DEBUG) return;
|
|
1418
1882
|
try {
|
|
1419
|
-
appendFileSync(
|
|
1420
|
-
|
|
1883
|
+
appendFileSync(
|
|
1884
|
+
join3(stateDir(), "ghfind-debug.log"),
|
|
1885
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
1886
|
+
`,
|
|
1887
|
+
"utf-8"
|
|
1888
|
+
);
|
|
1421
1889
|
} catch {
|
|
1422
1890
|
}
|
|
1423
1891
|
}
|
|
@@ -1475,7 +1943,13 @@ function save() {
|
|
|
1475
1943
|
}
|
|
1476
1944
|
function addNotification(type, message) {
|
|
1477
1945
|
if (notifs.length === 0) load();
|
|
1478
|
-
const n = {
|
|
1946
|
+
const n = {
|
|
1947
|
+
id: nextId++,
|
|
1948
|
+
type,
|
|
1949
|
+
icon: iconFor(type),
|
|
1950
|
+
message,
|
|
1951
|
+
timestamp: Date.now()
|
|
1952
|
+
};
|
|
1479
1953
|
notifs.unshift(n);
|
|
1480
1954
|
if (notifs.length > MAX_NOTIFS) notifs = notifs.slice(0, MAX_NOTIFS);
|
|
1481
1955
|
save();
|
|
@@ -1530,10 +2004,15 @@ function toTracked(raw) {
|
|
|
1530
2004
|
}));
|
|
1531
2005
|
}
|
|
1532
2006
|
async function fetchReleases(fullName, etag, token) {
|
|
1533
|
-
const headers = {
|
|
2007
|
+
const headers = {
|
|
2008
|
+
Accept: "application/vnd.github+json"
|
|
2009
|
+
};
|
|
1534
2010
|
if (etag) headers["If-None-Match"] = etag;
|
|
1535
2011
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1536
|
-
const res = await fetch(
|
|
2012
|
+
const res = await fetch(
|
|
2013
|
+
`https://api.github.com/repos/${fullName}/releases?per_page=${PER_REPO}`,
|
|
2014
|
+
{ headers }
|
|
2015
|
+
);
|
|
1537
2016
|
if (res.status === 304 || !res.ok) return { status: res.status };
|
|
1538
2017
|
return {
|
|
1539
2018
|
status: 200,
|
|
@@ -1552,19 +2031,37 @@ async function checkReleases(opts = {}) {
|
|
|
1552
2031
|
const prev = cache[bm.repo.fullName];
|
|
1553
2032
|
const lastSeen = getLastSeen(bm.repo.fullName);
|
|
1554
2033
|
const isNew = (r) => !r.publishedAt || new Date(r.publishedAt).getTime() > lastSeen;
|
|
1555
|
-
const { status, etag, releases } = await fetchReleases(
|
|
2034
|
+
const { status, etag, releases } = await fetchReleases(
|
|
2035
|
+
bm.repo.fullName,
|
|
2036
|
+
prev?.etag,
|
|
2037
|
+
token
|
|
2038
|
+
);
|
|
1556
2039
|
if (status === 304 && prev) {
|
|
1557
|
-
results2.push({
|
|
2040
|
+
results2.push({
|
|
2041
|
+
fullName: bm.repo.fullName,
|
|
2042
|
+
newReleases: prev.releases.filter(isNew)
|
|
2043
|
+
});
|
|
1558
2044
|
continue;
|
|
1559
2045
|
}
|
|
1560
2046
|
if (status !== 200 || !releases) {
|
|
1561
|
-
results2.push({
|
|
2047
|
+
results2.push({
|
|
2048
|
+
fullName: bm.repo.fullName,
|
|
2049
|
+
newReleases: [],
|
|
2050
|
+
error: `HTTP ${status}`
|
|
2051
|
+
});
|
|
1562
2052
|
continue;
|
|
1563
2053
|
}
|
|
1564
2054
|
cache[bm.repo.fullName] = { checkedAt: Date.now(), etag, releases };
|
|
1565
|
-
results2.push({
|
|
2055
|
+
results2.push({
|
|
2056
|
+
fullName: bm.repo.fullName,
|
|
2057
|
+
newReleases: releases.filter(isNew)
|
|
2058
|
+
});
|
|
1566
2059
|
} catch (err) {
|
|
1567
|
-
results2.push({
|
|
2060
|
+
results2.push({
|
|
2061
|
+
fullName: bm.repo.fullName,
|
|
2062
|
+
newReleases: [],
|
|
2063
|
+
error: err.message
|
|
2064
|
+
});
|
|
1568
2065
|
}
|
|
1569
2066
|
}
|
|
1570
2067
|
saveCache(cache);
|
|
@@ -1586,81 +2083,142 @@ async function checkAndNotify(opts = {}) {
|
|
|
1586
2083
|
function allCachedReleases() {
|
|
1587
2084
|
const cache = loadCache();
|
|
1588
2085
|
const bookmarks = getBookmarks();
|
|
1589
|
-
return Object.entries(cache).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
|
|
2086
|
+
return Object.entries(cache).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
|
|
2087
|
+
(a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
|
|
2088
|
+
);
|
|
1590
2089
|
}
|
|
1591
2090
|
|
|
1592
2091
|
// src/init.ts
|
|
1593
|
-
import { createInterface } from "readline";
|
|
2092
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1594
2093
|
|
|
1595
|
-
// src/
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
2094
|
+
// src/login.ts
|
|
2095
|
+
import { createInterface } from "readline";
|
|
2096
|
+
import { homedir as homedir2 } from "os";
|
|
2097
|
+
import { join as join4 } from "path";
|
|
2098
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2099
|
+
var TOKEN_PREFIXES = ["ghp_", "gho_", "github_pat_"];
|
|
2100
|
+
function isLikelyGithubToken(token) {
|
|
2101
|
+
return TOKEN_PREFIXES.some((prefix) => token.startsWith(prefix));
|
|
2102
|
+
}
|
|
2103
|
+
function maskToken(token) {
|
|
2104
|
+
if (token.length <= 11) return `${token.slice(0, 4)}\u2026`;
|
|
2105
|
+
return `${token.slice(0, 7)}\u2026${token.slice(-4)}`;
|
|
2106
|
+
}
|
|
2107
|
+
function ghCliConfigDir() {
|
|
2108
|
+
return process.env.GH_CONFIG_DIR || join4(homedir2(), ".config", "gh");
|
|
2109
|
+
}
|
|
2110
|
+
function parseHostsYmlToken(text, host = "github.com") {
|
|
2111
|
+
let inHost = false;
|
|
2112
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2113
|
+
const trimmed = line.trim();
|
|
2114
|
+
if (/^[A-Za-z0-9.-]+:\s*$/.test(trimmed)) {
|
|
2115
|
+
inHost = trimmed.slice(0, -1).trim() === host;
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
if (inHost) {
|
|
2119
|
+
const match = trimmed.match(/^oauth_token:\s*["']?([^"'\s]+)/);
|
|
2120
|
+
if (match) return match[1];
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return void 0;
|
|
2124
|
+
}
|
|
2125
|
+
function readGhCliToken(ghDir = ghCliConfigDir(), host = process.env.GH_HOST ?? "github.com") {
|
|
2126
|
+
const hostsFile = join4(ghDir, "hosts.yml");
|
|
2127
|
+
if (!existsSync3(hostsFile)) return void 0;
|
|
2128
|
+
try {
|
|
2129
|
+
return parseHostsYmlToken(readFileSync4(hostsFile, "utf-8"), host);
|
|
2130
|
+
} catch {
|
|
2131
|
+
return void 0;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
function persistToken(token, deps) {
|
|
2135
|
+
const config = deps.readConfig();
|
|
2136
|
+
config.githubToken = token;
|
|
2137
|
+
deps.writeConfig(config);
|
|
2138
|
+
}
|
|
2139
|
+
function createAsk() {
|
|
2140
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2141
|
+
return (query) => new Promise((resolve) => {
|
|
2142
|
+
rl.question(query, (answer) => {
|
|
2143
|
+
rl.close();
|
|
2144
|
+
resolve(answer.trim());
|
|
2145
|
+
});
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function defaultLoginDeps() {
|
|
2149
|
+
return {
|
|
2150
|
+
ask: createAsk(),
|
|
2151
|
+
readGhCliToken: () => readGhCliToken(),
|
|
2152
|
+
readConfig: loadConfig,
|
|
2153
|
+
writeConfig: (config) => saveConfig(config)
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
function successNote() {
|
|
2157
|
+
return [
|
|
2158
|
+
" Authentication raises the search rate limit from 60 to",
|
|
2159
|
+
" 5,000 requests/hour. Remove the token anytime by editing",
|
|
2160
|
+
` ${configPath()} \u2014 delete the "githubToken" line and save.`
|
|
2161
|
+
];
|
|
2162
|
+
}
|
|
2163
|
+
async function runLoginWizard(deps = defaultLoginDeps()) {
|
|
2164
|
+
const { ask: ask2 } = deps;
|
|
2165
|
+
console.log("");
|
|
2166
|
+
console.log(" ghfind login");
|
|
2167
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2168
|
+
console.log("");
|
|
2169
|
+
if (process.env.GITHUB_TOKEN) {
|
|
2170
|
+
console.log(
|
|
2171
|
+
" \u2139 GITHUB_TOKEN is set in the environment and already takes precedence."
|
|
2172
|
+
);
|
|
2173
|
+
console.log("");
|
|
1652
2174
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2175
|
+
const current = deps.readConfig();
|
|
2176
|
+
if (current.githubToken) {
|
|
2177
|
+
console.log(
|
|
2178
|
+
` \u2713 A token is already saved: ${maskToken(current.githubToken)}`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
const ghToken = deps.readGhCliToken();
|
|
2182
|
+
if (ghToken) {
|
|
2183
|
+
console.log(" \u2713 Found a token managed by the GitHub CLI (gh).");
|
|
2184
|
+
const answer = await ask2(" Import it into ghfind? [Y/n]: ");
|
|
2185
|
+
if (answer.toLowerCase() !== "n") {
|
|
2186
|
+
persistToken(ghToken, deps);
|
|
2187
|
+
finishLogin(ghToken);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
const pasted = await ask2(" Paste a GitHub token (Enter to skip): ");
|
|
2192
|
+
if (!pasted) {
|
|
2193
|
+
console.log(" Skipped \u2014 no token saved.");
|
|
2194
|
+
console.log("");
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
if (!isLikelyGithubToken(pasted)) {
|
|
2198
|
+
console.log(
|
|
2199
|
+
` \u26A0 That doesn't look like a GitHub token (expected one of: ${TOKEN_PREFIXES.join(", ")}).`
|
|
2200
|
+
);
|
|
2201
|
+
const confirm = await ask2(" Save it anyway? [y/N]: ");
|
|
2202
|
+
if (confirm.toLowerCase() !== "y") {
|
|
2203
|
+
console.log(" Cancelled \u2014 nothing saved.");
|
|
2204
|
+
console.log("");
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
persistToken(pasted, deps);
|
|
2209
|
+
finishLogin(pasted);
|
|
1656
2210
|
}
|
|
1657
|
-
function
|
|
1658
|
-
|
|
2211
|
+
function finishLogin(token) {
|
|
2212
|
+
console.log(` \u2713 Token saved to ${configPath()}`);
|
|
2213
|
+
console.log(` (masked: ${maskToken(token)})`);
|
|
2214
|
+
console.log("");
|
|
2215
|
+
for (const line of successNote()) console.log(line);
|
|
2216
|
+
console.log("");
|
|
1659
2217
|
}
|
|
1660
2218
|
|
|
1661
2219
|
// src/init.ts
|
|
1662
2220
|
function ask(query) {
|
|
1663
|
-
const rl =
|
|
2221
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1664
2222
|
return new Promise((resolve) => {
|
|
1665
2223
|
rl.question(query, (answer) => {
|
|
1666
2224
|
rl.close();
|
|
@@ -1674,10 +2232,14 @@ async function runInitWizard() {
|
|
|
1674
2232
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1675
2233
|
console.log("");
|
|
1676
2234
|
const token = await ask(" GitHub token (optional, press Enter to skip): ");
|
|
1677
|
-
if (token && !
|
|
1678
|
-
console.log(
|
|
2235
|
+
if (token && !isLikelyGithubToken(token)) {
|
|
2236
|
+
console.log(
|
|
2237
|
+
" \u26A0 Token doesn't look like a GitHub token (should start with ghp_, gho_, or github_pat_)"
|
|
2238
|
+
);
|
|
1679
2239
|
}
|
|
1680
|
-
const sortStr = await ask(
|
|
2240
|
+
const sortStr = await ask(
|
|
2241
|
+
" Default sort [best-match/stars/updated/forks] (best-match): "
|
|
2242
|
+
);
|
|
1681
2243
|
const limitStr = await ask(" Default result limit [10-100] (50): ");
|
|
1682
2244
|
const themes = listThemes();
|
|
1683
2245
|
const themeStr = await ask(` Theme [${themes.join("/")}] (tokyo-night): `);
|
|
@@ -1690,7 +2252,7 @@ async function runInitWizard() {
|
|
|
1690
2252
|
};
|
|
1691
2253
|
if (token) config.githubToken = token;
|
|
1692
2254
|
saveConfig(config);
|
|
1693
|
-
const { configPath: configPath2 } = await import("./config-
|
|
2255
|
+
const { configPath: configPath2 } = await import("./config-OCIPF2IF.js");
|
|
1694
2256
|
console.log(` \u2713 Config saved to ${configPath2()}`);
|
|
1695
2257
|
console.log(" Run 'ghfind' to start browsing!");
|
|
1696
2258
|
console.log("");
|
|
@@ -1698,16 +2260,35 @@ async function runInitWizard() {
|
|
|
1698
2260
|
|
|
1699
2261
|
// src/compare.ts
|
|
1700
2262
|
function buildComparisonTable(repos) {
|
|
1701
|
-
if (repos.length < 2)
|
|
2263
|
+
if (repos.length < 2)
|
|
2264
|
+
return "Select at least 2 repos to compare (press c to add).";
|
|
1702
2265
|
const rows = [];
|
|
1703
2266
|
const names = repos.map((r) => r.fullName);
|
|
1704
|
-
rows.push({
|
|
1705
|
-
|
|
2267
|
+
rows.push({
|
|
2268
|
+
label: "Stars",
|
|
2269
|
+
values: repos.map((r) => `\u2605 ${r.stars.toLocaleString()}`)
|
|
2270
|
+
});
|
|
2271
|
+
rows.push({
|
|
2272
|
+
label: "Forks",
|
|
2273
|
+
values: repos.map((r) => r.forks.toLocaleString())
|
|
2274
|
+
});
|
|
1706
2275
|
rows.push({ label: "Language", values: repos.map((r) => r.language ?? "\u2014") });
|
|
1707
|
-
rows.push({
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2276
|
+
rows.push({
|
|
2277
|
+
label: "Created",
|
|
2278
|
+
values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "\u2014")
|
|
2279
|
+
});
|
|
2280
|
+
rows.push({
|
|
2281
|
+
label: "Updated",
|
|
2282
|
+
values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "\u2014")
|
|
2283
|
+
});
|
|
2284
|
+
rows.push({
|
|
2285
|
+
label: "Topics",
|
|
2286
|
+
values: repos.map((r) => (r.topics?.slice(0, 5).join(", ") ?? "\u2014") || "\u2014")
|
|
2287
|
+
});
|
|
2288
|
+
rows.push({
|
|
2289
|
+
label: "Description",
|
|
2290
|
+
values: repos.map((r) => r.description?.slice(0, 50) ?? "\u2014")
|
|
2291
|
+
});
|
|
1711
2292
|
rows.push({ label: "URL", values: repos.map((r) => r.url || "\u2014") });
|
|
1712
2293
|
const colW = Math.max(35, ...names.map((n) => n.length));
|
|
1713
2294
|
const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
|
|
@@ -1718,7 +2299,10 @@ function buildComparisonTable(repos) {
|
|
|
1718
2299
|
"\u251C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u253C") + "\u2524"
|
|
1719
2300
|
];
|
|
1720
2301
|
for (const row of rows) {
|
|
1721
|
-
const cells = [
|
|
2302
|
+
const cells = [
|
|
2303
|
+
row.label.padEnd(14),
|
|
2304
|
+
...row.values.map((v) => v.padEnd(colW))
|
|
2305
|
+
];
|
|
1722
2306
|
lines.push("\u2502" + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
|
|
1723
2307
|
}
|
|
1724
2308
|
lines.push("\u2514" + sep.map((s) => "\u2500" + s + "\u2500").join("\u2534") + "\u2518");
|
|
@@ -1727,8 +2311,8 @@ function buildComparisonTable(repos) {
|
|
|
1727
2311
|
|
|
1728
2312
|
// src/cli.ts
|
|
1729
2313
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1730
|
-
import { join as join6, dirname as
|
|
1731
|
-
import { fileURLToPath as
|
|
2314
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
2315
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1732
2316
|
|
|
1733
2317
|
// src/tui.ts
|
|
1734
2318
|
import {
|
|
@@ -1764,7 +2348,8 @@ function tabSince(tab) {
|
|
|
1764
2348
|
}
|
|
1765
2349
|
}
|
|
1766
2350
|
function fmtStars(n) {
|
|
1767
|
-
if (n >= 1e6)
|
|
2351
|
+
if (n >= 1e6)
|
|
2352
|
+
return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
|
|
1768
2353
|
if (n >= 999500) return "1M";
|
|
1769
2354
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
|
|
1770
2355
|
return String(n);
|
|
@@ -1941,8 +2526,13 @@ function deleteHistoryEntry(index) {
|
|
|
1941
2526
|
const entries = readHistory();
|
|
1942
2527
|
if (index < 0 || index >= entries.length) return;
|
|
1943
2528
|
const removed = entries[index];
|
|
1944
|
-
const remaining = entries.filter(
|
|
1945
|
-
|
|
2529
|
+
const remaining = entries.filter(
|
|
2530
|
+
(e) => e.timestamp !== removed.timestamp || e.query !== removed.query
|
|
2531
|
+
);
|
|
2532
|
+
writeRaw(
|
|
2533
|
+
HISTORY_FILE,
|
|
2534
|
+
remaining.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
2535
|
+
);
|
|
1946
2536
|
}
|
|
1947
2537
|
function clearHistory() {
|
|
1948
2538
|
writeRaw(HISTORY_FILE, "");
|
|
@@ -1951,7 +2541,10 @@ function rotateHistory() {
|
|
|
1951
2541
|
const entries = readHistory();
|
|
1952
2542
|
if (entries.length <= MAX_ENTRIES) return;
|
|
1953
2543
|
const trimmed = entries.slice(0, MAX_ENTRIES);
|
|
1954
|
-
writeRaw(
|
|
2544
|
+
writeRaw(
|
|
2545
|
+
HISTORY_FILE,
|
|
2546
|
+
trimmed.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
2547
|
+
);
|
|
1955
2548
|
}
|
|
1956
2549
|
|
|
1957
2550
|
// src/saved-searches.ts
|
|
@@ -1963,15 +2556,30 @@ function saveRaw2(searches) {
|
|
|
1963
2556
|
writeJSON(SAVED_FILE, searches);
|
|
1964
2557
|
}
|
|
1965
2558
|
function getSavedSearches() {
|
|
1966
|
-
return loadRaw2().sort(
|
|
2559
|
+
return loadRaw2().sort(
|
|
2560
|
+
(a, b) => (b.lastRunAt ?? b.createdAt) - (a.lastRunAt ?? a.createdAt)
|
|
2561
|
+
);
|
|
1967
2562
|
}
|
|
1968
2563
|
function saveSearch(name, query, mode, sort, limit, tab) {
|
|
1969
2564
|
const searches = loadRaw2();
|
|
1970
2565
|
const now = Date.now();
|
|
1971
2566
|
const existing = searches.findIndex((s) => s.name === name);
|
|
1972
|
-
const entry = {
|
|
2567
|
+
const entry = {
|
|
2568
|
+
name,
|
|
2569
|
+
query,
|
|
2570
|
+
mode,
|
|
2571
|
+
sort,
|
|
2572
|
+
limit,
|
|
2573
|
+
tab,
|
|
2574
|
+
createdAt: now,
|
|
2575
|
+
lastRunAt: now
|
|
2576
|
+
};
|
|
1973
2577
|
if (existing >= 0) {
|
|
1974
|
-
searches[existing] = {
|
|
2578
|
+
searches[existing] = {
|
|
2579
|
+
...searches[existing],
|
|
2580
|
+
...entry,
|
|
2581
|
+
createdAt: searches[existing].createdAt
|
|
2582
|
+
};
|
|
1975
2583
|
} else {
|
|
1976
2584
|
searches.push(entry);
|
|
1977
2585
|
}
|
|
@@ -1997,7 +2605,9 @@ function saveSession(state) {
|
|
|
1997
2605
|
writeJSON(SESSION_FILE, state);
|
|
1998
2606
|
} catch (err) {
|
|
1999
2607
|
if (process.env.DEBUG)
|
|
2000
|
-
console.error(
|
|
2608
|
+
console.error(
|
|
2609
|
+
`[ghfind] Failed to save session: ${err instanceof Error ? err.message : String(err)}`
|
|
2610
|
+
);
|
|
2001
2611
|
}
|
|
2002
2612
|
}
|
|
2003
2613
|
function restoreSession() {
|
|
@@ -2006,16 +2616,31 @@ function restoreSession() {
|
|
|
2006
2616
|
|
|
2007
2617
|
// src/deepdive.ts
|
|
2008
2618
|
var USER_AGENT = "ghfind/1.0";
|
|
2619
|
+
function buildHeaders(token) {
|
|
2620
|
+
const headers = { "User-Agent": USER_AGENT };
|
|
2621
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2622
|
+
return headers;
|
|
2623
|
+
}
|
|
2009
2624
|
async function fetchDeepDive(repo, token) {
|
|
2010
|
-
const
|
|
2011
|
-
const headers = { "User-Agent": USER_AGENT, ...auth };
|
|
2625
|
+
const headers = buildHeaders(token);
|
|
2012
2626
|
const [languagesRes, contributorsRes] = await Promise.all([
|
|
2013
|
-
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/languages`, {
|
|
2014
|
-
|
|
2627
|
+
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/languages`, {
|
|
2628
|
+
headers
|
|
2629
|
+
}),
|
|
2630
|
+
fetch(
|
|
2631
|
+
`https://api.github.com/repos/${repo.owner}/${repo.name}/contributors?per_page=5`,
|
|
2632
|
+
{ headers }
|
|
2633
|
+
)
|
|
2015
2634
|
]);
|
|
2016
2635
|
let readmeText = "";
|
|
2017
|
-
for (const path of [
|
|
2018
|
-
|
|
2636
|
+
for (const path of [
|
|
2637
|
+
`${repo.owner}/${repo.name}/main/README.md`,
|
|
2638
|
+
`${repo.owner}/${repo.name}/master/README.md`,
|
|
2639
|
+
`${repo.owner}/${repo.name}/main/README.rst`
|
|
2640
|
+
]) {
|
|
2641
|
+
const r = await fetch(`https://raw.githubusercontent.com/${path}`, {
|
|
2642
|
+
headers
|
|
2643
|
+
});
|
|
2019
2644
|
if (r.ok) {
|
|
2020
2645
|
readmeText = await r.text();
|
|
2021
2646
|
break;
|
|
@@ -2023,8 +2648,12 @@ async function fetchDeepDive(repo, token) {
|
|
|
2023
2648
|
}
|
|
2024
2649
|
return {
|
|
2025
2650
|
summary: formatSummary(repo),
|
|
2026
|
-
languages: formatLanguages(
|
|
2027
|
-
|
|
2651
|
+
languages: formatLanguages(
|
|
2652
|
+
languagesRes.ok ? await languagesRes.json().catch(() => ({})) : {}
|
|
2653
|
+
),
|
|
2654
|
+
contributors: formatContributors(
|
|
2655
|
+
contributorsRes.ok ? await contributorsRes.json().catch(() => []) : []
|
|
2656
|
+
),
|
|
2028
2657
|
readme: formatReadme(readmeText)
|
|
2029
2658
|
};
|
|
2030
2659
|
}
|
|
@@ -2058,7 +2687,8 @@ function formatReadme(readme) {
|
|
|
2058
2687
|
if (!readme) return " (no README)";
|
|
2059
2688
|
const stripped = readme.replace(/```[\s\S]*?```/g, "[code block]").replace(/#{1,6}\s/g, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").replace(/`([^`]+)`/g, "$1").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2060
2689
|
const lines = stripped.slice(0, 30);
|
|
2061
|
-
if (stripped.length > 30)
|
|
2690
|
+
if (stripped.length > 30)
|
|
2691
|
+
lines.push("... (more lines, open in browser to read full)");
|
|
2062
2692
|
return lines.map((l) => ` ${l}`).join("\n");
|
|
2063
2693
|
}
|
|
2064
2694
|
function buildDeepDiveText(data) {
|
|
@@ -2113,12 +2743,7 @@ function renderMarkdown(md, opts = {}) {
|
|
|
2113
2743
|
const colCount = Math.max(...dataRows.map((r) => r.length));
|
|
2114
2744
|
const colW = [];
|
|
2115
2745
|
for (let ci = 0; ci < colCount; ci++) {
|
|
2116
|
-
colW.push(
|
|
2117
|
-
Math.max(
|
|
2118
|
-
3,
|
|
2119
|
-
...dataRows.map((r) => (r[ci] ?? "").length)
|
|
2120
|
-
)
|
|
2121
|
-
);
|
|
2746
|
+
colW.push(Math.max(3, ...dataRows.map((r) => (r[ci] ?? "").length)));
|
|
2122
2747
|
}
|
|
2123
2748
|
const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
|
|
2124
2749
|
if (totalW > width) {
|
|
@@ -2250,12 +2875,15 @@ function renderMarkdown(md, opts = {}) {
|
|
|
2250
2875
|
|
|
2251
2876
|
// src/explore.ts
|
|
2252
2877
|
async function fetchTopics() {
|
|
2253
|
-
const res = await fetch(
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2878
|
+
const res = await fetch(
|
|
2879
|
+
"https://api.github.com/search/topics?q=popular&per_page=30",
|
|
2880
|
+
{
|
|
2881
|
+
headers: {
|
|
2882
|
+
Accept: "application/vnd.github.mercy-preview+json",
|
|
2883
|
+
"User-Agent": "search-cli"
|
|
2884
|
+
}
|
|
2257
2885
|
}
|
|
2258
|
-
|
|
2886
|
+
);
|
|
2259
2887
|
if (!res.ok) throw new Error(`Topics API: HTTP ${res.status}`);
|
|
2260
2888
|
const data = await res.json();
|
|
2261
2889
|
return (data.items ?? []).map((item) => ({
|
|
@@ -2282,7 +2910,9 @@ var StatusManager = class {
|
|
|
2282
2910
|
this.type = type;
|
|
2283
2911
|
this.message = message;
|
|
2284
2912
|
if (type === "searching" || type === "loading") {
|
|
2285
|
-
this.startSpinner(
|
|
2913
|
+
this.startSpinner(
|
|
2914
|
+
type === "searching" ? SEARCH_SPINNER : LOADING_SPINNER
|
|
2915
|
+
);
|
|
2286
2916
|
} else {
|
|
2287
2917
|
this.emit();
|
|
2288
2918
|
}
|
|
@@ -2328,9 +2958,11 @@ function formatShare(repo, format2) {
|
|
|
2328
2958
|
async function copyToClipboard(text) {
|
|
2329
2959
|
try {
|
|
2330
2960
|
const p = process.platform;
|
|
2331
|
-
const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await
|
|
2961
|
+
const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await findLinuxClipboard();
|
|
2332
2962
|
if (!cmd) return false;
|
|
2333
|
-
const proc = spawn2(cmd[0], cmd.slice(1), {
|
|
2963
|
+
const proc = spawn2(cmd[0], cmd.slice(1), {
|
|
2964
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2965
|
+
});
|
|
2334
2966
|
proc.stdin.write(text);
|
|
2335
2967
|
proc.stdin.end();
|
|
2336
2968
|
return new Promise((resolve) => {
|
|
@@ -2341,6 +2973,20 @@ async function copyToClipboard(text) {
|
|
|
2341
2973
|
return false;
|
|
2342
2974
|
}
|
|
2343
2975
|
}
|
|
2976
|
+
async function findLinuxClipboard() {
|
|
2977
|
+
const { execFileSync } = await import("child_process");
|
|
2978
|
+
try {
|
|
2979
|
+
execFileSync("xclip", ["-version"], { stdio: "ignore" });
|
|
2980
|
+
return ["xclip", "-selection", "clipboard"];
|
|
2981
|
+
} catch {
|
|
2982
|
+
try {
|
|
2983
|
+
execFileSync("wl-copy", ["--version"], { stdio: "ignore" });
|
|
2984
|
+
return ["wl-copy"];
|
|
2985
|
+
} catch {
|
|
2986
|
+
return null;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2344
2990
|
|
|
2345
2991
|
// src/tips.ts
|
|
2346
2992
|
var TipRotator = class {
|
|
@@ -2375,20 +3021,15 @@ function nextTip() {
|
|
|
2375
3021
|
return defaultRotator.next();
|
|
2376
3022
|
}
|
|
2377
3023
|
|
|
2378
|
-
// src/tui.ts
|
|
2379
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
2380
|
-
import { join as join5, dirname as dirname4 } from "path";
|
|
2381
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2382
|
-
|
|
2383
3024
|
// src/update-check.ts
|
|
2384
|
-
import { readFileSync as
|
|
2385
|
-
import { join as
|
|
3025
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3026
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
2386
3027
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2387
3028
|
var REGISTRY_URL = "https://registry.npmjs.org/github-search-cli/latest";
|
|
2388
3029
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2389
3030
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
2390
3031
|
var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
|
|
2391
|
-
var PKG_DIR2 =
|
|
3032
|
+
var PKG_DIR2 = join5(__dirname2, "..");
|
|
2392
3033
|
var STATE_FILE = "update-state.json";
|
|
2393
3034
|
var DEFAULTS = {
|
|
2394
3035
|
lastCheck: 0,
|
|
@@ -2423,7 +3064,8 @@ function isNewerVersion(current, latest) {
|
|
|
2423
3064
|
return false;
|
|
2424
3065
|
}
|
|
2425
3066
|
async function checkForUpdate(currentVersion) {
|
|
2426
|
-
if (process.env.DEBUG)
|
|
3067
|
+
if (process.env.DEBUG)
|
|
3068
|
+
debugLog(`Checking for update (current: ${currentVersion})`);
|
|
2427
3069
|
try {
|
|
2428
3070
|
const res = await fetch(REGISTRY_URL, {
|
|
2429
3071
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
@@ -2442,7 +3084,10 @@ async function checkForUpdate(currentVersion) {
|
|
|
2442
3084
|
if (!isNewerVersion(currentVersion, latest)) return null;
|
|
2443
3085
|
return latest;
|
|
2444
3086
|
} catch (e) {
|
|
2445
|
-
if (process.env.DEBUG)
|
|
3087
|
+
if (process.env.DEBUG)
|
|
3088
|
+
debugLog(
|
|
3089
|
+
`Update check failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3090
|
+
);
|
|
2446
3091
|
return null;
|
|
2447
3092
|
}
|
|
2448
3093
|
}
|
|
@@ -2466,19 +3111,28 @@ function suppressUpdateNotices() {
|
|
|
2466
3111
|
});
|
|
2467
3112
|
}
|
|
2468
3113
|
async function performUpdate() {
|
|
3114
|
+
const pkg = "github-search-cli";
|
|
2469
3115
|
const isBun = typeof process.versions !== "undefined" && "bun" in process.versions;
|
|
2470
|
-
const cmd = isBun ? ["bun", "install", "-g",
|
|
3116
|
+
const cmd = isBun ? ["bun", "install", "-g", pkg] : ["npm", "install", "-g", pkg];
|
|
2471
3117
|
try {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
3118
|
+
if (isBun) {
|
|
3119
|
+
const proc = Bun.spawn(cmd, {
|
|
3120
|
+
stdout: "pipe",
|
|
3121
|
+
stderr: "pipe"
|
|
3122
|
+
});
|
|
3123
|
+
await proc.exited;
|
|
3124
|
+
if (proc.exitCode !== 0) {
|
|
3125
|
+
const err = await proc.stderr.text();
|
|
3126
|
+
debugLog(`Install failed (code ${proc.exitCode}): ${err}`);
|
|
3127
|
+
}
|
|
3128
|
+
return proc.exitCode === 0;
|
|
2480
3129
|
}
|
|
2481
|
-
|
|
3130
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
3131
|
+
return await new Promise((resolve) => {
|
|
3132
|
+
const proc = spawn3(cmd[0], cmd.slice(1), { stdio: "ignore" });
|
|
3133
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
3134
|
+
proc.on("error", () => resolve(false));
|
|
3135
|
+
});
|
|
2482
3136
|
} catch {
|
|
2483
3137
|
return false;
|
|
2484
3138
|
}
|
|
@@ -2491,14 +3145,14 @@ function recordPreUpdateState(currentVersion) {
|
|
|
2491
3145
|
}
|
|
2492
3146
|
function fetchReleaseNotes(version) {
|
|
2493
3147
|
try {
|
|
2494
|
-
const notesPath =
|
|
2495
|
-
const md =
|
|
3148
|
+
const notesPath = join5(PKG_DIR2, "release-notes", `${version}.md`);
|
|
3149
|
+
const md = readFileSync5(notesPath, "utf-8");
|
|
2496
3150
|
if (md.trim().length > 0) return md;
|
|
2497
3151
|
} catch {
|
|
2498
3152
|
}
|
|
2499
3153
|
try {
|
|
2500
|
-
const changelogPath =
|
|
2501
|
-
const changelog =
|
|
3154
|
+
const changelogPath = join5(PKG_DIR2, "CHANGELOG.md");
|
|
3155
|
+
const changelog = readFileSync5(changelogPath, "utf-8");
|
|
2502
3156
|
const header = "## [" + version + "]";
|
|
2503
3157
|
const start = changelog.indexOf(header);
|
|
2504
3158
|
if (start === -1) return null;
|
|
@@ -2513,8 +3167,6 @@ function fetchReleaseNotes(version) {
|
|
|
2513
3167
|
}
|
|
2514
3168
|
|
|
2515
3169
|
// src/tui.ts
|
|
2516
|
-
var __dirname3 = dirname4(fileURLToPath4(import.meta.url));
|
|
2517
|
-
var PKG_DIR3 = join5(__dirname3, "..");
|
|
2518
3170
|
var cachedVersion = null;
|
|
2519
3171
|
var colors = {
|
|
2520
3172
|
bg: "#3D3B3B",
|
|
@@ -2557,6 +3209,7 @@ var SORT_MODES = [
|
|
|
2557
3209
|
{ key: "forks", label: "forks" }
|
|
2558
3210
|
];
|
|
2559
3211
|
async function launchBrowser(theme_override) {
|
|
3212
|
+
const terminalBg = await detectTerminalBackground();
|
|
2560
3213
|
let renderer;
|
|
2561
3214
|
try {
|
|
2562
3215
|
const isTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
@@ -2592,24 +3245,22 @@ async function launchBrowser(theme_override) {
|
|
|
2592
3245
|
}
|
|
2593
3246
|
const root = renderer.root;
|
|
2594
3247
|
root.flexDirection = "column";
|
|
2595
|
-
root.border = true;
|
|
2596
|
-
root.borderTop = true;
|
|
2597
|
-
root.borderBottom = true;
|
|
2598
|
-
root.borderLeft = true;
|
|
2599
|
-
root.borderRight = true;
|
|
2600
|
-
root.borderColor = colors.borderAccent;
|
|
2601
|
-
root.backgroundColor = colors.bg;
|
|
2602
3248
|
const config = loadConfig();
|
|
2603
3249
|
const githubToken = config.githubToken || process.env.GITHUB_TOKEN;
|
|
2604
3250
|
const theme = loadTheme(theme_override || config.theme);
|
|
2605
3251
|
Object.assign(colors, theme);
|
|
3252
|
+
if (terminalBg) {
|
|
3253
|
+
colors.bg = terminalBg;
|
|
3254
|
+
Object.assign(colors, deriveSurfaceLayers(terminalBg));
|
|
3255
|
+
}
|
|
3256
|
+
renderer.setBackgroundColor(colors.bg);
|
|
2606
3257
|
const session = restoreSession();
|
|
2607
3258
|
let currentRepos = [];
|
|
2608
3259
|
let currentSort = session?.sort ?? config.defaultSort;
|
|
2609
3260
|
let currentLimit = session?.limit ?? config.defaultLimit;
|
|
2610
3261
|
let currentQueryInput = session?.query ?? "";
|
|
2611
3262
|
let isLoading = false;
|
|
2612
|
-
let currentMode = session?.mode
|
|
3263
|
+
let currentMode = session?.mode === "trending" || session?.mode === "packages" ? session.mode : "search";
|
|
2613
3264
|
let packages = [];
|
|
2614
3265
|
let currentPackageSort = "best-match";
|
|
2615
3266
|
let packageResultsRaw = [];
|
|
@@ -2625,11 +3276,9 @@ async function launchBrowser(theme_override) {
|
|
|
2625
3276
|
let quitArmed = false;
|
|
2626
3277
|
const header = new TextRenderable3(renderer, {
|
|
2627
3278
|
content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
height: 1
|
|
2631
|
-
borderBottom: true,
|
|
2632
|
-
borderBottomColor: colors.border
|
|
3279
|
+
bg: colors.bg,
|
|
3280
|
+
fg: colors.muted,
|
|
3281
|
+
height: 1
|
|
2633
3282
|
});
|
|
2634
3283
|
const trendingTabBox = new BoxRenderable3(renderer, {
|
|
2635
3284
|
flexDirection: "row",
|
|
@@ -2648,8 +3297,8 @@ async function launchBrowser(theme_override) {
|
|
|
2648
3297
|
const label = ` ${name} `;
|
|
2649
3298
|
const tt = new TextRenderable3(renderer, {
|
|
2650
3299
|
content: label,
|
|
2651
|
-
|
|
2652
|
-
|
|
3300
|
+
fg: isActive ? colors.bg : colors.muted,
|
|
3301
|
+
bg: isActive ? colors.blue : colors.bg,
|
|
2653
3302
|
height: 1
|
|
2654
3303
|
});
|
|
2655
3304
|
trendingTabTexts.push(tt);
|
|
@@ -2657,8 +3306,8 @@ async function launchBrowser(theme_override) {
|
|
|
2657
3306
|
if (i < TAB_NAMES.length - 1) {
|
|
2658
3307
|
const sp = new TextRenderable3(renderer, {
|
|
2659
3308
|
content: " ",
|
|
2660
|
-
|
|
2661
|
-
|
|
3309
|
+
fg: colors.muted,
|
|
3310
|
+
bg: colors.bg,
|
|
2662
3311
|
height: 1
|
|
2663
3312
|
});
|
|
2664
3313
|
trendingTabTexts.push(sp);
|
|
@@ -2668,20 +3317,13 @@ async function launchBrowser(theme_override) {
|
|
|
2668
3317
|
}
|
|
2669
3318
|
root.add(header);
|
|
2670
3319
|
root.add(trendingTabBox);
|
|
2671
|
-
const gapBox = new BoxRenderable3(renderer, {
|
|
2672
|
-
height: 1,
|
|
2673
|
-
visible: true
|
|
2674
|
-
});
|
|
2675
|
-
root.add(gapBox);
|
|
2676
3320
|
const searchBox = new BoxRenderable3(renderer, {
|
|
2677
3321
|
visible: true,
|
|
2678
|
-
bordered: true,
|
|
2679
|
-
borderColor: colors.border,
|
|
2680
|
-
title: "",
|
|
2681
|
-
titleColor: colors.blue,
|
|
2682
3322
|
width: "100%",
|
|
2683
3323
|
flexDirection: "row",
|
|
2684
3324
|
marginTop: 1,
|
|
3325
|
+
paddingY: 1,
|
|
3326
|
+
paddingLeft: 1,
|
|
2685
3327
|
backgroundColor: colors.surface
|
|
2686
3328
|
});
|
|
2687
3329
|
const searchInput = new InputRenderable(renderer, {
|
|
@@ -2689,8 +3331,6 @@ async function launchBrowser(theme_override) {
|
|
|
2689
3331
|
value: "",
|
|
2690
3332
|
backgroundColor: colors.surface,
|
|
2691
3333
|
textColor: colors.text,
|
|
2692
|
-
borderColor: colors.blue,
|
|
2693
|
-
selectedBorderColor: colors.blue,
|
|
2694
3334
|
paddingX: 0,
|
|
2695
3335
|
flexGrow: 1
|
|
2696
3336
|
});
|
|
@@ -2700,12 +3340,8 @@ async function launchBrowser(theme_override) {
|
|
|
2700
3340
|
content: formatToolbar(currentSort, currentLimit, totalCount),
|
|
2701
3341
|
visible: true,
|
|
2702
3342
|
// hidden in trending mode
|
|
2703
|
-
|
|
2704
|
-
// Surface panel
|
|
2705
|
-
color: colors.muted,
|
|
3343
|
+
fg: colors.muted,
|
|
2706
3344
|
height: 1,
|
|
2707
|
-
borderBottom: true,
|
|
2708
|
-
borderBottomColor: colors.border,
|
|
2709
3345
|
paddingX: 1
|
|
2710
3346
|
});
|
|
2711
3347
|
root.add(toolbarText);
|
|
@@ -2717,9 +3353,8 @@ async function launchBrowser(theme_override) {
|
|
|
2717
3353
|
paddingX: 1
|
|
2718
3354
|
});
|
|
2719
3355
|
const resultsBox = new BoxRenderable3(renderer, {
|
|
2720
|
-
|
|
3356
|
+
border: true,
|
|
2721
3357
|
borderColor: colors.border,
|
|
2722
|
-
borderBottomColor: colors.border,
|
|
2723
3358
|
title: " Results ",
|
|
2724
3359
|
titleColor: colors.blue,
|
|
2725
3360
|
width: "50%",
|
|
@@ -2747,11 +3382,6 @@ async function launchBrowser(theme_override) {
|
|
|
2747
3382
|
});
|
|
2748
3383
|
resultsBox.add(resultsSelect);
|
|
2749
3384
|
const detailBox = new BoxRenderable3(renderer, {
|
|
2750
|
-
bordered: true,
|
|
2751
|
-
borderColor: colors.border,
|
|
2752
|
-
borderBottomColor: colors.border,
|
|
2753
|
-
title: " Details ",
|
|
2754
|
-
titleColor: colors.green,
|
|
2755
3385
|
width: "50%",
|
|
2756
3386
|
flexDirection: "column",
|
|
2757
3387
|
paddingX: 1,
|
|
@@ -2761,7 +3391,7 @@ async function launchBrowser(theme_override) {
|
|
|
2761
3391
|
});
|
|
2762
3392
|
const detailText = new TextRenderable3(renderer, {
|
|
2763
3393
|
content: "",
|
|
2764
|
-
|
|
3394
|
+
fg: colors.text,
|
|
2765
3395
|
wrapMode: "none"
|
|
2766
3396
|
});
|
|
2767
3397
|
detailBox.add(detailText);
|
|
@@ -2785,8 +3415,8 @@ async function launchBrowser(theme_override) {
|
|
|
2785
3415
|
"\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554",
|
|
2786
3416
|
" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
2787
3417
|
].join("\n"),
|
|
2788
|
-
|
|
2789
|
-
|
|
3418
|
+
fg: colors.accent,
|
|
3419
|
+
bg: colors.bg,
|
|
2790
3420
|
height: 6
|
|
2791
3421
|
});
|
|
2792
3422
|
landingBox.add(landingBanner);
|
|
@@ -2841,7 +3471,7 @@ async function launchBrowser(theme_override) {
|
|
|
2841
3471
|
const spacerTop = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
2842
3472
|
const titleLine = new TextRenderable3(renderer, {
|
|
2843
3473
|
content: opt.title,
|
|
2844
|
-
|
|
3474
|
+
fg: i === 0 ? colors.accent : colors.text,
|
|
2845
3475
|
height: 1
|
|
2846
3476
|
});
|
|
2847
3477
|
const spacerBot = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
@@ -2851,8 +3481,8 @@ async function launchBrowser(theme_override) {
|
|
|
2851
3481
|
}
|
|
2852
3482
|
const landingHint = new TextRenderable3(renderer, {
|
|
2853
3483
|
content: " \u2191\u2193 Navigate \u21E9 Select [?]help [q]uit",
|
|
2854
|
-
|
|
2855
|
-
|
|
3484
|
+
fg: colors.muted,
|
|
3485
|
+
bg: colors.bg,
|
|
2856
3486
|
height: 1,
|
|
2857
3487
|
marginTop: 1
|
|
2858
3488
|
});
|
|
@@ -2871,10 +3501,10 @@ async function launchBrowser(theme_override) {
|
|
|
2871
3501
|
for (let i = 0; i < landingCards.length; i++) {
|
|
2872
3502
|
const isSel = i === landingSelected;
|
|
2873
3503
|
landingCards[i].borderColor = isSel ? colors.accent : colors.border;
|
|
2874
|
-
const children = landingCards[i].
|
|
3504
|
+
const children = landingCards[i].getChildren();
|
|
2875
3505
|
const titleChild = children[1];
|
|
2876
3506
|
if (titleChild) {
|
|
2877
|
-
titleChild.
|
|
3507
|
+
titleChild.fg = isSel ? colors.accent : colors.text;
|
|
2878
3508
|
}
|
|
2879
3509
|
}
|
|
2880
3510
|
}
|
|
@@ -2885,7 +3515,10 @@ async function launchBrowser(theme_override) {
|
|
|
2885
3515
|
updateLandingCards();
|
|
2886
3516
|
renderer.requestRender();
|
|
2887
3517
|
} else if (key.name === "down" || key.name === "j") {
|
|
2888
|
-
landingSelected = Math.min(
|
|
3518
|
+
landingSelected = Math.min(
|
|
3519
|
+
landingOptions.length - 1,
|
|
3520
|
+
landingSelected + 1
|
|
3521
|
+
);
|
|
2889
3522
|
updateLandingCards();
|
|
2890
3523
|
renderer.requestRender();
|
|
2891
3524
|
} else if (key.name === "enter" || key.name === "return") {
|
|
@@ -2900,7 +3533,6 @@ async function launchBrowser(theme_override) {
|
|
|
2900
3533
|
});
|
|
2901
3534
|
function hideMainContent() {
|
|
2902
3535
|
trendingTabBox.visible = false;
|
|
2903
|
-
gapBox.visible = false;
|
|
2904
3536
|
searchBox.visible = false;
|
|
2905
3537
|
toolbarText.visible = false;
|
|
2906
3538
|
body.visible = false;
|
|
@@ -2910,7 +3542,6 @@ async function launchBrowser(theme_override) {
|
|
|
2910
3542
|
trendingTabBox.visible = true;
|
|
2911
3543
|
} else {
|
|
2912
3544
|
searchBox.visible = true;
|
|
2913
|
-
gapBox.visible = true;
|
|
2914
3545
|
toolbarText.visible = true;
|
|
2915
3546
|
}
|
|
2916
3547
|
body.visible = true;
|
|
@@ -2936,7 +3567,6 @@ async function launchBrowser(theme_override) {
|
|
|
2936
3567
|
landingBox.visible = true;
|
|
2937
3568
|
body.visible = false;
|
|
2938
3569
|
trendingTabBox.visible = false;
|
|
2939
|
-
gapBox.visible = false;
|
|
2940
3570
|
searchBox.visible = false;
|
|
2941
3571
|
toolbarText.visible = false;
|
|
2942
3572
|
} else {
|
|
@@ -2972,10 +3602,11 @@ async function launchBrowser(theme_override) {
|
|
|
2972
3602
|
leaderBox.visible = true;
|
|
2973
3603
|
} else if (type === "readme") {
|
|
2974
3604
|
readmeBox.visible = true;
|
|
3605
|
+
} else if (type === "org") {
|
|
3606
|
+
orgBox.visible = true;
|
|
2975
3607
|
} else if (type === "update") {
|
|
2976
3608
|
updateDim.visible = true;
|
|
2977
3609
|
updateBox.visible = true;
|
|
2978
|
-
updateSelect.focus();
|
|
2979
3610
|
}
|
|
2980
3611
|
renderer.requestRender();
|
|
2981
3612
|
}
|
|
@@ -2983,9 +3614,7 @@ async function launchBrowser(theme_override) {
|
|
|
2983
3614
|
const helpBox = new BoxRenderable3(renderer, {
|
|
2984
3615
|
visible: false,
|
|
2985
3616
|
flexGrow: 1,
|
|
2986
|
-
backgroundColor: colors.bg
|
|
2987
|
-
borderBottom: true,
|
|
2988
|
-
borderBottomColor: colors.border
|
|
3617
|
+
backgroundColor: colors.bg
|
|
2989
3618
|
});
|
|
2990
3619
|
const helpScroll = new ScrollBoxRenderable3(renderer, {
|
|
2991
3620
|
flexGrow: 1,
|
|
@@ -2996,15 +3625,15 @@ async function launchBrowser(theme_override) {
|
|
|
2996
3625
|
viewportOptions: { backgroundColor: colors.bg },
|
|
2997
3626
|
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
2998
3627
|
scrollbarOptions: {
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3628
|
+
width: 1,
|
|
3629
|
+
trackOptions: {
|
|
3630
|
+
backgroundColor: colors.bg,
|
|
3631
|
+
foregroundColor: colors.muted
|
|
3632
|
+
}
|
|
3002
3633
|
}
|
|
3003
3634
|
});
|
|
3004
3635
|
for (const section of helpSections) {
|
|
3005
3636
|
const sectionBox = new BoxRenderable3(renderer, {
|
|
3006
|
-
borderBottom: true,
|
|
3007
|
-
borderBottomColor: colors.border,
|
|
3008
3637
|
title: section.title,
|
|
3009
3638
|
titleColor: colors[section.titleColor] ?? section.titleColor,
|
|
3010
3639
|
flexDirection: "column",
|
|
@@ -3018,7 +3647,7 @@ async function launchBrowser(theme_override) {
|
|
|
3018
3647
|
sectionBox.add(
|
|
3019
3648
|
new TextRenderable3(renderer, {
|
|
3020
3649
|
content: ` ${keys} ${row.action}`,
|
|
3021
|
-
|
|
3650
|
+
fg: colors.text
|
|
3022
3651
|
})
|
|
3023
3652
|
);
|
|
3024
3653
|
}
|
|
@@ -3026,7 +3655,7 @@ async function launchBrowser(theme_override) {
|
|
|
3026
3655
|
sectionBox.add(
|
|
3027
3656
|
new TextRenderable3(renderer, {
|
|
3028
3657
|
content: ` ${section.note}`,
|
|
3029
|
-
|
|
3658
|
+
fg: colors.muted
|
|
3030
3659
|
})
|
|
3031
3660
|
);
|
|
3032
3661
|
}
|
|
@@ -3038,8 +3667,6 @@ async function launchBrowser(theme_override) {
|
|
|
3038
3667
|
visible: false,
|
|
3039
3668
|
flexGrow: 1,
|
|
3040
3669
|
backgroundColor: colors.bg,
|
|
3041
|
-
borderBottom: true,
|
|
3042
|
-
borderBottomColor: colors.border,
|
|
3043
3670
|
title: " Search History ",
|
|
3044
3671
|
titleColor: colors.blue
|
|
3045
3672
|
});
|
|
@@ -3075,8 +3702,6 @@ async function launchBrowser(theme_override) {
|
|
|
3075
3702
|
visible: false,
|
|
3076
3703
|
flexGrow: 1,
|
|
3077
3704
|
backgroundColor: colors.bg,
|
|
3078
|
-
borderBottom: true,
|
|
3079
|
-
borderBottomColor: colors.border,
|
|
3080
3705
|
title: " Bookmarks ",
|
|
3081
3706
|
titleColor: colors.green
|
|
3082
3707
|
});
|
|
@@ -3112,8 +3737,6 @@ async function launchBrowser(theme_override) {
|
|
|
3112
3737
|
visible: false,
|
|
3113
3738
|
flexGrow: 1,
|
|
3114
3739
|
backgroundColor: colors.bg,
|
|
3115
|
-
borderBottom: true,
|
|
3116
|
-
borderBottomColor: colors.border,
|
|
3117
3740
|
title: " Saved Searches ",
|
|
3118
3741
|
titleColor: colors.yellow
|
|
3119
3742
|
});
|
|
@@ -3134,8 +3757,6 @@ async function launchBrowser(theme_override) {
|
|
|
3134
3757
|
visible: false,
|
|
3135
3758
|
flexGrow: 1,
|
|
3136
3759
|
backgroundColor: colors.bg,
|
|
3137
|
-
borderBottom: true,
|
|
3138
|
-
borderBottomColor: colors.border,
|
|
3139
3760
|
title: " Topics ",
|
|
3140
3761
|
titleColor: colors.purple ?? colors.blue
|
|
3141
3762
|
});
|
|
@@ -3171,8 +3792,6 @@ async function launchBrowser(theme_override) {
|
|
|
3171
3792
|
visible: false,
|
|
3172
3793
|
flexGrow: 1,
|
|
3173
3794
|
backgroundColor: colors.bg,
|
|
3174
|
-
borderBottom: true,
|
|
3175
|
-
borderBottomColor: colors.border,
|
|
3176
3795
|
title: " Export ",
|
|
3177
3796
|
titleColor: colors.green
|
|
3178
3797
|
});
|
|
@@ -3210,15 +3829,13 @@ async function launchBrowser(theme_override) {
|
|
|
3210
3829
|
visible: false,
|
|
3211
3830
|
flexGrow: 1,
|
|
3212
3831
|
backgroundColor: colors.bg,
|
|
3213
|
-
borderBottom: true,
|
|
3214
|
-
borderBottomColor: colors.border,
|
|
3215
3832
|
title: " Comparison ",
|
|
3216
3833
|
titleColor: colors.yellow
|
|
3217
3834
|
});
|
|
3218
3835
|
const compareText = new TextRenderable3(renderer, {
|
|
3219
3836
|
content: "",
|
|
3220
|
-
|
|
3221
|
-
|
|
3837
|
+
fg: colors.text,
|
|
3838
|
+
bg: colors.bg
|
|
3222
3839
|
});
|
|
3223
3840
|
compareBox.add(compareText);
|
|
3224
3841
|
root.add(compareBox);
|
|
@@ -3226,15 +3843,13 @@ async function launchBrowser(theme_override) {
|
|
|
3226
3843
|
visible: false,
|
|
3227
3844
|
flexGrow: 1,
|
|
3228
3845
|
backgroundColor: colors.bg,
|
|
3229
|
-
borderBottom: true,
|
|
3230
|
-
borderBottomColor: colors.border,
|
|
3231
3846
|
title: " Notifications ",
|
|
3232
3847
|
titleColor: colors.yellow
|
|
3233
3848
|
});
|
|
3234
3849
|
const notifsText = new TextRenderable3(renderer, {
|
|
3235
3850
|
content: "(no notifications)",
|
|
3236
|
-
|
|
3237
|
-
|
|
3851
|
+
fg: colors.text,
|
|
3852
|
+
bg: colors.bg
|
|
3238
3853
|
});
|
|
3239
3854
|
notifsBox.add(notifsText);
|
|
3240
3855
|
root.add(notifsBox);
|
|
@@ -3250,8 +3865,6 @@ async function launchBrowser(theme_override) {
|
|
|
3250
3865
|
visible: false,
|
|
3251
3866
|
flexGrow: 1,
|
|
3252
3867
|
backgroundColor: colors.bg,
|
|
3253
|
-
borderBottom: true,
|
|
3254
|
-
borderBottomColor: colors.border,
|
|
3255
3868
|
title: " Share Repo ",
|
|
3256
3869
|
titleColor: colors.blue
|
|
3257
3870
|
});
|
|
@@ -3352,11 +3965,9 @@ async function launchBrowser(theme_override) {
|
|
|
3352
3965
|
});
|
|
3353
3966
|
const leaderFooter = new TextRenderable3(renderer, {
|
|
3354
3967
|
content: " \u2191\u2193 select Enter run Esc/q close ",
|
|
3355
|
-
|
|
3356
|
-
|
|
3968
|
+
fg: colors.muted,
|
|
3969
|
+
bg: colors.surfaceDim,
|
|
3357
3970
|
height: 1,
|
|
3358
|
-
borderTop: true,
|
|
3359
|
-
borderTopColor: colors.border,
|
|
3360
3971
|
paddingX: 2
|
|
3361
3972
|
});
|
|
3362
3973
|
leaderBox.add(leaderSelect);
|
|
@@ -3394,8 +4005,8 @@ async function launchBrowser(theme_override) {
|
|
|
3394
4005
|
});
|
|
3395
4006
|
const updateText = new TextRenderable3(renderer, {
|
|
3396
4007
|
content: "",
|
|
3397
|
-
|
|
3398
|
-
|
|
4008
|
+
fg: colors.accent,
|
|
4009
|
+
bg: colors.surfaceDim
|
|
3399
4010
|
});
|
|
3400
4011
|
let updateSelectedOption = 0;
|
|
3401
4012
|
const updateOptionsRow = new BoxRenderable3(renderer, {
|
|
@@ -3416,8 +4027,8 @@ async function launchBrowser(theme_override) {
|
|
|
3416
4027
|
});
|
|
3417
4028
|
const updateNowText = new TextRenderable3(renderer, {
|
|
3418
4029
|
content: " Update Now ",
|
|
3419
|
-
|
|
3420
|
-
|
|
4030
|
+
fg: updateSelectedOption === 0 ? colors.yellow : colors.text,
|
|
4031
|
+
bg: colors.surfaceDim
|
|
3421
4032
|
});
|
|
3422
4033
|
updateNowBox.add(updateNowText);
|
|
3423
4034
|
const laterBox = new BoxRenderable3(renderer, {
|
|
@@ -3431,8 +4042,8 @@ async function launchBrowser(theme_override) {
|
|
|
3431
4042
|
});
|
|
3432
4043
|
const laterText = new TextRenderable3(renderer, {
|
|
3433
4044
|
content: " Later",
|
|
3434
|
-
|
|
3435
|
-
|
|
4045
|
+
fg: updateSelectedOption === 1 ? colors.yellow : colors.text,
|
|
4046
|
+
bg: colors.surfaceDim
|
|
3436
4047
|
});
|
|
3437
4048
|
laterBox.add(laterText);
|
|
3438
4049
|
const neverBox = new BoxRenderable3(renderer, {
|
|
@@ -3446,25 +4057,25 @@ async function launchBrowser(theme_override) {
|
|
|
3446
4057
|
});
|
|
3447
4058
|
const neverText = new TextRenderable3(renderer, {
|
|
3448
4059
|
content: " Don't show again",
|
|
3449
|
-
|
|
3450
|
-
|
|
4060
|
+
fg: updateSelectedOption === 2 ? colors.yellow : colors.text,
|
|
4061
|
+
bg: colors.surfaceDim
|
|
3451
4062
|
});
|
|
3452
4063
|
neverBox.add(neverText);
|
|
3453
4064
|
function renderUpdateOptions() {
|
|
3454
4065
|
updateNowBox.borderColor = updateSelectedOption === 0 ? colors.yellow : colors.border;
|
|
3455
4066
|
laterBox.borderColor = updateSelectedOption === 1 ? colors.yellow : colors.border;
|
|
3456
4067
|
neverBox.borderColor = updateSelectedOption === 2 ? colors.yellow : colors.border;
|
|
3457
|
-
updateNowText.
|
|
3458
|
-
laterText.
|
|
3459
|
-
neverText.
|
|
4068
|
+
updateNowText.fg = updateSelectedOption === 0 ? colors.yellow : colors.text;
|
|
4069
|
+
laterText.fg = updateSelectedOption === 1 ? colors.yellow : colors.text;
|
|
4070
|
+
neverText.fg = updateSelectedOption === 2 ? colors.yellow : colors.text;
|
|
3460
4071
|
renderer.requestRender();
|
|
3461
4072
|
}
|
|
3462
4073
|
updateOptionsRow.add(updateNowBox);
|
|
3463
4074
|
updateOptionsRow.add(laterBox);
|
|
3464
4075
|
const updateFooter = new TextRenderable3(renderer, {
|
|
3465
4076
|
content: " \u2190\u2192 select Enter Esc/q close",
|
|
3466
|
-
|
|
3467
|
-
|
|
4077
|
+
fg: colors.muted,
|
|
4078
|
+
bg: colors.surfaceDim,
|
|
3468
4079
|
height: 1,
|
|
3469
4080
|
paddingX: 1
|
|
3470
4081
|
});
|
|
@@ -3623,6 +4234,19 @@ async function launchBrowser(theme_override) {
|
|
|
3623
4234
|
});
|
|
3624
4235
|
}
|
|
3625
4236
|
repoItems.push(
|
|
4237
|
+
{
|
|
4238
|
+
type: "action",
|
|
4239
|
+
name: "Org profile",
|
|
4240
|
+
description: "Org summary for the selected repo's owner",
|
|
4241
|
+
action: () => {
|
|
4242
|
+
const opt = resultsSelect.getSelectedOption();
|
|
4243
|
+
const repo = opt?.value;
|
|
4244
|
+
if (repo?.owner) {
|
|
4245
|
+
showOverlay("none");
|
|
4246
|
+
showOrgProfile(repo.owner);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
},
|
|
3626
4250
|
{
|
|
3627
4251
|
type: "action",
|
|
3628
4252
|
name: "Bookmark",
|
|
@@ -3653,15 +4277,28 @@ async function launchBrowser(theme_override) {
|
|
|
3653
4277
|
});
|
|
3654
4278
|
}
|
|
3655
4279
|
const navItems = [];
|
|
3656
|
-
navItems.push(
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
4280
|
+
navItems.push(
|
|
4281
|
+
{
|
|
4282
|
+
type: "action",
|
|
4283
|
+
name: "Packages",
|
|
4284
|
+
description: "Search npm packages",
|
|
4285
|
+
action: () => {
|
|
4286
|
+
showOverlay("none");
|
|
4287
|
+
showPackagesMode();
|
|
4288
|
+
}
|
|
4289
|
+
},
|
|
4290
|
+
{
|
|
4291
|
+
type: "action",
|
|
4292
|
+
name: "Org profile",
|
|
4293
|
+
description: "Look up any GitHub organization",
|
|
4294
|
+
action: () => {
|
|
4295
|
+
showOverlay("none");
|
|
4296
|
+
const opt = resultsSelect.getSelectedOption();
|
|
4297
|
+
const repo = opt?.value;
|
|
4298
|
+
showOrgProfile(repo?.owner ?? currentQueryInput.trim());
|
|
4299
|
+
}
|
|
3663
4300
|
}
|
|
3664
|
-
|
|
4301
|
+
);
|
|
3665
4302
|
if (currentMode === "search") {
|
|
3666
4303
|
navItems.push({
|
|
3667
4304
|
type: "action",
|
|
@@ -3758,13 +4395,7 @@ async function launchBrowser(theme_override) {
|
|
|
3758
4395
|
name: "Quit",
|
|
3759
4396
|
description: "Exit ghfind",
|
|
3760
4397
|
action: () => {
|
|
3761
|
-
saveSession(
|
|
3762
|
-
mode: currentMode,
|
|
3763
|
-
query: currentQueryInput,
|
|
3764
|
-
sort: currentSort,
|
|
3765
|
-
limit: currentLimit,
|
|
3766
|
-
trendingTab
|
|
3767
|
-
});
|
|
4398
|
+
saveSession(buildSessionState());
|
|
3768
4399
|
cleanup();
|
|
3769
4400
|
}
|
|
3770
4401
|
}
|
|
@@ -3789,12 +4420,7 @@ async function launchBrowser(theme_override) {
|
|
|
3789
4420
|
const state = readUpdateState();
|
|
3790
4421
|
if (state.suppressed) return;
|
|
3791
4422
|
if (!cachedVersion) {
|
|
3792
|
-
|
|
3793
|
-
const pkg = JSON.parse(readFileSync5(join5(PKG_DIR3, "package.json"), "utf-8"));
|
|
3794
|
-
cachedVersion = pkg.version;
|
|
3795
|
-
} catch {
|
|
3796
|
-
return;
|
|
3797
|
-
}
|
|
4423
|
+
cachedVersion = getVersion();
|
|
3798
4424
|
}
|
|
3799
4425
|
const currentVersion2 = cachedVersion;
|
|
3800
4426
|
if (!state.lastInstalledVersion) return;
|
|
@@ -3811,13 +4437,7 @@ ${notesBlock}`;
|
|
|
3811
4437
|
async function checkForUpdateAndShow(force = false) {
|
|
3812
4438
|
if (!force && !shouldCheckUpdate()) return;
|
|
3813
4439
|
if (cachedVersion === null) {
|
|
3814
|
-
|
|
3815
|
-
const pkg = JSON.parse(readFileSync5(join5(PKG_DIR3, "package.json"), "utf-8"));
|
|
3816
|
-
cachedVersion = pkg.version;
|
|
3817
|
-
} catch {
|
|
3818
|
-
debugLog("Failed to read package.json");
|
|
3819
|
-
return;
|
|
3820
|
-
}
|
|
4440
|
+
cachedVersion = getVersion();
|
|
3821
4441
|
}
|
|
3822
4442
|
const currentVersion2 = cachedVersion;
|
|
3823
4443
|
const latest = process.env.DEBUG_FORCE_UPDATE || await checkForUpdate(currentVersion2);
|
|
@@ -3889,7 +4509,7 @@ ${notesBlock}`;
|
|
|
3889
4509
|
saveSearch(
|
|
3890
4510
|
name,
|
|
3891
4511
|
currentQueryInput,
|
|
3892
|
-
currentMode,
|
|
4512
|
+
currentMode === "trending" ? "trending" : "search",
|
|
3893
4513
|
currentSort,
|
|
3894
4514
|
currentLimit,
|
|
3895
4515
|
currentMode === "trending" ? trendingTab : void 0
|
|
@@ -3919,12 +4539,106 @@ ${notesBlock}`;
|
|
|
3919
4539
|
renderer.requestRender();
|
|
3920
4540
|
}
|
|
3921
4541
|
}
|
|
4542
|
+
const orgBox = new BoxRenderable3(renderer, {
|
|
4543
|
+
visible: false,
|
|
4544
|
+
flexGrow: 1,
|
|
4545
|
+
backgroundColor: colors.surface,
|
|
4546
|
+
title: " Org profile ",
|
|
4547
|
+
titleColor: colors.green
|
|
4548
|
+
});
|
|
4549
|
+
const orgScroll = new ScrollBoxRenderable3(renderer, {
|
|
4550
|
+
flexGrow: 1,
|
|
4551
|
+
backgroundColor: colors.bg,
|
|
4552
|
+
scrollY: true,
|
|
4553
|
+
scrollX: false,
|
|
4554
|
+
paddingX: 1,
|
|
4555
|
+
viewportOptions: { backgroundColor: colors.bg },
|
|
4556
|
+
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
4557
|
+
scrollbarOptions: {
|
|
4558
|
+
width: 1,
|
|
4559
|
+
trackOptions: {
|
|
4560
|
+
backgroundColor: colors.bg,
|
|
4561
|
+
foregroundColor: colors.muted
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
});
|
|
4565
|
+
const orgText = new TextRenderable3(renderer, {
|
|
4566
|
+
content: "",
|
|
4567
|
+
fg: colors.text,
|
|
4568
|
+
bg: colors.bg
|
|
4569
|
+
});
|
|
4570
|
+
orgScroll.add(orgText);
|
|
4571
|
+
orgBox.add(orgScroll);
|
|
4572
|
+
const orgFooter = new TextRenderable3(renderer, {
|
|
4573
|
+
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
4574
|
+
fg: colors.muted,
|
|
4575
|
+
bg: colors.bg,
|
|
4576
|
+
height: 1,
|
|
4577
|
+
paddingX: 1
|
|
4578
|
+
});
|
|
4579
|
+
orgBox.add(orgFooter);
|
|
4580
|
+
root.add(orgBox);
|
|
4581
|
+
function formatOrgSummary(p) {
|
|
4582
|
+
const lines = [];
|
|
4583
|
+
const title = p.name ? `${p.login} \u2014 ${p.name}` : p.login;
|
|
4584
|
+
lines.push(title);
|
|
4585
|
+
if (p.description) lines.push(p.description);
|
|
4586
|
+
if (p.location) lines.push(`Location ${p.location}`);
|
|
4587
|
+
if (p.blog) lines.push(`Web ${p.blog}`);
|
|
4588
|
+
if (p.createdAt) lines.push(`Created ${p.createdAt.slice(0, 10)}`);
|
|
4589
|
+
lines.push(
|
|
4590
|
+
`Repos ${p.publicRepoCount.toLocaleString()} public${p.fetchedRepoCount < p.publicRepoCount ? ` (aggregated ${p.fetchedRepoCount})` : ""}`
|
|
4591
|
+
);
|
|
4592
|
+
lines.push(
|
|
4593
|
+
`Stars ${p.totalStars.toLocaleString()} (sum of aggregated repos)`
|
|
4594
|
+
);
|
|
4595
|
+
lines.push(
|
|
4596
|
+
`Forks ${p.totalForks.toLocaleString()} (sum of aggregated repos)`
|
|
4597
|
+
);
|
|
4598
|
+
if (p.topLanguages.length > 0) {
|
|
4599
|
+
lines.push(
|
|
4600
|
+
`Languages ${p.topLanguages.map((l) => `${l.language} (${l.repos})`).join(", ")}`
|
|
4601
|
+
);
|
|
4602
|
+
}
|
|
4603
|
+
if (p.topRepos.length > 0) {
|
|
4604
|
+
lines.push("");
|
|
4605
|
+
lines.push("Top repos by stars:");
|
|
4606
|
+
for (const r of p.topRepos) {
|
|
4607
|
+
lines.push(
|
|
4608
|
+
` ${r.fullName} \u2605 ${r.stars.toLocaleString()} ${r.language ?? ""}`
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
if (p.activeRepos.length > 0) {
|
|
4613
|
+
lines.push("");
|
|
4614
|
+
lines.push("Recently active:");
|
|
4615
|
+
for (const r of p.activeRepos) {
|
|
4616
|
+
lines.push(` ${r.fullName} pushed ${r.pushedAt.slice(0, 10)}`);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
return lines.join("\n");
|
|
4620
|
+
}
|
|
4621
|
+
async function showOrgProfile(owner) {
|
|
4622
|
+
const name = owner.trim().replace(/^@/, "");
|
|
4623
|
+
if (!name) {
|
|
4624
|
+
setStatus("No org selected");
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
orgBox.title = ` Org profile \u2014 ${name} `;
|
|
4628
|
+
orgText.content = ` Loading org profile for ${name}...`;
|
|
4629
|
+
showOverlay("org");
|
|
4630
|
+
try {
|
|
4631
|
+
const profile = await fetchOrgProfile(name, { token: githubToken });
|
|
4632
|
+
orgText.content = formatOrgSummary(profile);
|
|
4633
|
+
} catch (err) {
|
|
4634
|
+
orgText.content = ` Failed to load org profile: ${err instanceof Error ? err.message : String(err)}`;
|
|
4635
|
+
}
|
|
4636
|
+
renderer.requestRender();
|
|
4637
|
+
}
|
|
3922
4638
|
const readmeBox = new BoxRenderable3(renderer, {
|
|
3923
4639
|
visible: false,
|
|
3924
4640
|
flexGrow: 1,
|
|
3925
4641
|
backgroundColor: colors.surface,
|
|
3926
|
-
borderBottom: true,
|
|
3927
|
-
borderBottomColor: colors.border,
|
|
3928
4642
|
title: " README ",
|
|
3929
4643
|
titleColor: colors.green
|
|
3930
4644
|
});
|
|
@@ -3937,22 +4651,24 @@ ${notesBlock}`;
|
|
|
3937
4651
|
viewportOptions: { backgroundColor: colors.bg },
|
|
3938
4652
|
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
3939
4653
|
scrollbarOptions: {
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
4654
|
+
width: 1,
|
|
4655
|
+
trackOptions: {
|
|
4656
|
+
backgroundColor: colors.bg,
|
|
4657
|
+
foregroundColor: colors.muted
|
|
4658
|
+
}
|
|
3943
4659
|
}
|
|
3944
4660
|
});
|
|
3945
4661
|
const readmeText = new TextRenderable3(renderer, {
|
|
3946
4662
|
content: "",
|
|
3947
|
-
|
|
3948
|
-
|
|
4663
|
+
fg: colors.text,
|
|
4664
|
+
bg: colors.bg
|
|
3949
4665
|
});
|
|
3950
4666
|
readmeScroll.add(readmeText);
|
|
3951
4667
|
readmeBox.add(readmeScroll);
|
|
3952
4668
|
const readmeFooter = new TextRenderable3(renderer, {
|
|
3953
4669
|
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
3954
|
-
|
|
3955
|
-
|
|
4670
|
+
fg: colors.muted,
|
|
4671
|
+
bg: colors.bg,
|
|
3956
4672
|
height: 1,
|
|
3957
4673
|
paddingX: 1
|
|
3958
4674
|
});
|
|
@@ -3997,12 +4713,9 @@ ${notesBlock}`;
|
|
|
3997
4713
|
}
|
|
3998
4714
|
const statusBar = new TextRenderable3(renderer, {
|
|
3999
4715
|
content: " Ready. Press Enter to search.",
|
|
4000
|
-
|
|
4001
|
-
color: colors.muted,
|
|
4716
|
+
fg: colors.muted,
|
|
4002
4717
|
height: 1,
|
|
4003
|
-
paddingX: 1
|
|
4004
|
-
borderTop: true,
|
|
4005
|
-
borderTopColor: colors.border
|
|
4718
|
+
paddingX: 1
|
|
4006
4719
|
});
|
|
4007
4720
|
root.add(statusBar);
|
|
4008
4721
|
const statusMgr = new StatusManager((text) => {
|
|
@@ -4019,6 +4732,16 @@ ${notesBlock}`;
|
|
|
4019
4732
|
function setStatus(msg) {
|
|
4020
4733
|
statusMgr.set("idle", msg);
|
|
4021
4734
|
}
|
|
4735
|
+
function buildSessionState() {
|
|
4736
|
+
const mode = currentMode === "trending" ? "trending" : currentMode === "packages" ? "packages" : "search";
|
|
4737
|
+
return {
|
|
4738
|
+
mode,
|
|
4739
|
+
query: currentQueryInput,
|
|
4740
|
+
sort: currentSort,
|
|
4741
|
+
limit: currentLimit,
|
|
4742
|
+
trendingTab
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4022
4745
|
function setToolbar() {
|
|
4023
4746
|
const sort = currentMode === "packages" ? currentPackageSort : currentSort;
|
|
4024
4747
|
toolbarText.content = formatToolbar(sort, currentLimit, totalCount);
|
|
@@ -4146,7 +4869,9 @@ ${notesBlock}`;
|
|
|
4146
4869
|
),
|
|
4147
4870
|
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`, {
|
|
4148
4871
|
headers
|
|
4149
|
-
}).then(
|
|
4872
|
+
}).then(
|
|
4873
|
+
(r) => r.ok ? r.clone().json() : null
|
|
4874
|
+
)
|
|
4150
4875
|
]);
|
|
4151
4876
|
let chartSection = "";
|
|
4152
4877
|
if (chartRes.ok) {
|
|
@@ -4157,12 +4882,16 @@ ${notesBlock}`;
|
|
|
4157
4882
|
const chartW = Math.max(20, Math.min(60, termW));
|
|
4158
4883
|
const chartH = 10;
|
|
4159
4884
|
const chartLines = buildChartString(chartCommitData, chartW, chartH);
|
|
4160
|
-
chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join(
|
|
4885
|
+
chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join(
|
|
4886
|
+
"\n"
|
|
4887
|
+
);
|
|
4161
4888
|
} else if (data?.message) {
|
|
4162
4889
|
chartSection = ["", `Chart unavailable: ${data.message}`].join("\n");
|
|
4163
4890
|
}
|
|
4164
4891
|
} else {
|
|
4165
|
-
chartSection = ["", `Chart unavailable (API ${chartRes.status})`].join(
|
|
4892
|
+
chartSection = ["", `Chart unavailable (API ${chartRes.status})`].join(
|
|
4893
|
+
"\n"
|
|
4894
|
+
);
|
|
4166
4895
|
}
|
|
4167
4896
|
const desc = repo.description ?? "";
|
|
4168
4897
|
const lang = repo.language ?? "\u2014";
|
|
@@ -4234,7 +4963,13 @@ ${notesBlock}`;
|
|
|
4234
4963
|
const searchModule = new SearchModule(new TrendingAdapter());
|
|
4235
4964
|
const response = await searchModule.search(
|
|
4236
4965
|
{ keywords: [], qualifiers: [], raw: "trending" },
|
|
4237
|
-
{
|
|
4966
|
+
{
|
|
4967
|
+
limit: 25,
|
|
4968
|
+
sort: "stars",
|
|
4969
|
+
json: false,
|
|
4970
|
+
verbose: false,
|
|
4971
|
+
trendingSince: tabSince(trendingTab)
|
|
4972
|
+
}
|
|
4238
4973
|
);
|
|
4239
4974
|
const fetched = response.repos;
|
|
4240
4975
|
const since = tabSince(trendingTab);
|
|
@@ -4274,7 +5009,6 @@ ${notesBlock}`;
|
|
|
4274
5009
|
function showSearchMode() {
|
|
4275
5010
|
currentMode = "search";
|
|
4276
5011
|
trendingTabBox.visible = false;
|
|
4277
|
-
gapBox.visible = false;
|
|
4278
5012
|
searchBox.visible = true;
|
|
4279
5013
|
toolbarText.visible = true;
|
|
4280
5014
|
body.visible = true;
|
|
@@ -4285,7 +5019,6 @@ ${notesBlock}`;
|
|
|
4285
5019
|
function showPackagesMode() {
|
|
4286
5020
|
currentMode = "packages";
|
|
4287
5021
|
trendingTabBox.visible = false;
|
|
4288
|
-
gapBox.visible = false;
|
|
4289
5022
|
searchBox.visible = true;
|
|
4290
5023
|
toolbarText.visible = true;
|
|
4291
5024
|
body.visible = true;
|
|
@@ -4293,7 +5026,11 @@ ${notesBlock}`;
|
|
|
4293
5026
|
searchInput.focus();
|
|
4294
5027
|
packages = [];
|
|
4295
5028
|
resultsSelect.options = [
|
|
4296
|
-
{
|
|
5029
|
+
{
|
|
5030
|
+
name: "",
|
|
5031
|
+
description: "Type a query and press Enter to search",
|
|
5032
|
+
value: null
|
|
5033
|
+
}
|
|
4297
5034
|
];
|
|
4298
5035
|
detailText.content = "";
|
|
4299
5036
|
renderer.requestRender();
|
|
@@ -4424,7 +5161,9 @@ ${notesBlock}`;
|
|
|
4424
5161
|
}
|
|
4425
5162
|
}
|
|
4426
5163
|
function changePackageSort() {
|
|
4427
|
-
const idx = PACKAGE_SORT_MODES.findIndex(
|
|
5164
|
+
const idx = PACKAGE_SORT_MODES.findIndex(
|
|
5165
|
+
(m) => m.key === currentPackageSort
|
|
5166
|
+
);
|
|
4428
5167
|
const next = PACKAGE_SORT_MODES[(idx + 1) % PACKAGE_SORT_MODES.length];
|
|
4429
5168
|
currentPackageSort = next.key;
|
|
4430
5169
|
setToolbar();
|
|
@@ -4533,6 +5272,9 @@ ${pack.description ?? ""}`;
|
|
|
4533
5272
|
return;
|
|
4534
5273
|
}
|
|
4535
5274
|
}
|
|
5275
|
+
if (currentOverlay === "org") {
|
|
5276
|
+
return;
|
|
5277
|
+
}
|
|
4536
5278
|
if (currentOverlay === "readme") {
|
|
4537
5279
|
return;
|
|
4538
5280
|
}
|
|
@@ -4752,13 +5494,7 @@ ${pack.description ?? ""}`;
|
|
|
4752
5494
|
toggleGraph();
|
|
4753
5495
|
return;
|
|
4754
5496
|
}
|
|
4755
|
-
saveSession(
|
|
4756
|
-
mode: currentMode,
|
|
4757
|
-
query: currentQueryInput,
|
|
4758
|
-
sort: currentSort,
|
|
4759
|
-
limit: currentLimit,
|
|
4760
|
-
trendingTab
|
|
4761
|
-
});
|
|
5497
|
+
saveSession(buildSessionState());
|
|
4762
5498
|
cleanup();
|
|
4763
5499
|
return;
|
|
4764
5500
|
}
|
|
@@ -4767,11 +5503,7 @@ ${pack.description ?? ""}`;
|
|
|
4767
5503
|
return;
|
|
4768
5504
|
}
|
|
4769
5505
|
if (key.name === "space" && !searchInput.focused) {
|
|
4770
|
-
|
|
4771
|
-
showOverlay("none");
|
|
4772
|
-
} else {
|
|
4773
|
-
showLeaderMenu();
|
|
4774
|
-
}
|
|
5506
|
+
showLeaderMenu();
|
|
4775
5507
|
return;
|
|
4776
5508
|
}
|
|
4777
5509
|
if (key.name === "t" && !searchInput.focused) {
|
|
@@ -4781,19 +5513,20 @@ ${pack.description ?? ""}`;
|
|
|
4781
5513
|
const next = themes[(idx + 1) % themes.length];
|
|
4782
5514
|
const nextTheme = loadTheme(next);
|
|
4783
5515
|
Object.assign(colors, nextTheme);
|
|
5516
|
+
if (terminalBg) {
|
|
5517
|
+
colors.bg = terminalBg;
|
|
5518
|
+
Object.assign(colors, deriveSurfaceLayers(terminalBg));
|
|
5519
|
+
}
|
|
5520
|
+
renderer.setBackgroundColor(colors.bg);
|
|
4784
5521
|
config.theme = next;
|
|
4785
5522
|
saveConfig(config);
|
|
4786
5523
|
renderer.requestRender();
|
|
4787
5524
|
return;
|
|
4788
5525
|
}
|
|
4789
5526
|
if (key.name === "c" && !searchInput.focused) {
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
toggleCompareOnSelected();
|
|
4794
|
-
refreshCompare();
|
|
4795
|
-
showOverlay("compare");
|
|
4796
|
-
}
|
|
5527
|
+
toggleCompareOnSelected();
|
|
5528
|
+
refreshCompare();
|
|
5529
|
+
showOverlay("compare");
|
|
4797
5530
|
return;
|
|
4798
5531
|
}
|
|
4799
5532
|
if (key.name === "?" || key.name === "h" && key.ctrl) {
|
|
@@ -4866,17 +5599,7 @@ ${pack.description ?? ""}`;
|
|
|
4866
5599
|
return;
|
|
4867
5600
|
}
|
|
4868
5601
|
});
|
|
4869
|
-
const
|
|
4870
|
-
dirname4(fileURLToPath4(import.meta.url)),
|
|
4871
|
-
"..",
|
|
4872
|
-
"package.json"
|
|
4873
|
-
);
|
|
4874
|
-
let currentVersion = "0.0.0";
|
|
4875
|
-
try {
|
|
4876
|
-
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
4877
|
-
currentVersion = pkg.version;
|
|
4878
|
-
} catch {
|
|
4879
|
-
}
|
|
5602
|
+
const currentVersion = getVersion();
|
|
4880
5603
|
if (!session || !session.mode) {
|
|
4881
5604
|
showLanding(true);
|
|
4882
5605
|
}
|
|
@@ -4926,8 +5649,8 @@ if (import.meta.main && false) {
|
|
|
4926
5649
|
|
|
4927
5650
|
// src/cli.ts
|
|
4928
5651
|
process.env.GHFIND_CLI_RUN = "1";
|
|
4929
|
-
var
|
|
4930
|
-
var
|
|
5652
|
+
var __dirname3 = dirname4(fileURLToPath4(import.meta.url));
|
|
5653
|
+
var PKG_DIR3 = join6(__dirname3, "..");
|
|
4931
5654
|
function parseArgs(args) {
|
|
4932
5655
|
const flags = {
|
|
4933
5656
|
query: "",
|
|
@@ -4944,6 +5667,7 @@ function parseArgs(args) {
|
|
|
4944
5667
|
releases: false,
|
|
4945
5668
|
interval: 300,
|
|
4946
5669
|
init: false,
|
|
5670
|
+
login: false,
|
|
4947
5671
|
version: false,
|
|
4948
5672
|
help: false,
|
|
4949
5673
|
pkg: false,
|
|
@@ -4991,6 +5715,9 @@ function parseArgs(args) {
|
|
|
4991
5715
|
case "init":
|
|
4992
5716
|
flags.init = true;
|
|
4993
5717
|
break;
|
|
5718
|
+
case "login":
|
|
5719
|
+
flags.login = true;
|
|
5720
|
+
break;
|
|
4994
5721
|
case "--limit": {
|
|
4995
5722
|
const parsed = parseInt(args[++i]);
|
|
4996
5723
|
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
@@ -5025,6 +5752,14 @@ function parseArgs(args) {
|
|
|
5025
5752
|
case "--completion":
|
|
5026
5753
|
flags.completion = args[++i];
|
|
5027
5754
|
break;
|
|
5755
|
+
case "org": {
|
|
5756
|
+
const parts = [];
|
|
5757
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
5758
|
+
parts.push(args[++i]);
|
|
5759
|
+
}
|
|
5760
|
+
flags.org = parts.join("-");
|
|
5761
|
+
break;
|
|
5762
|
+
}
|
|
5028
5763
|
case "--compare": {
|
|
5029
5764
|
const repos = [];
|
|
5030
5765
|
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
@@ -5047,10 +5782,7 @@ function parseArgs(args) {
|
|
|
5047
5782
|
async function main() {
|
|
5048
5783
|
const flags = parseArgs(process.argv.slice(2));
|
|
5049
5784
|
if (flags.version) {
|
|
5050
|
-
|
|
5051
|
-
readFileSync6(join6(PKG_DIR4, "package.json"), "utf-8")
|
|
5052
|
-
);
|
|
5053
|
-
console.log(`ghfind v${pkg.version}`);
|
|
5785
|
+
console.log(`ghfind v${getVersion()}`);
|
|
5054
5786
|
return;
|
|
5055
5787
|
}
|
|
5056
5788
|
if (flags.doctor) {
|
|
@@ -5072,10 +5804,12 @@ Usage:
|
|
|
5072
5804
|
ghfind --trending --json Trending repos as JSON
|
|
5073
5805
|
ghfind --releases Check bookmarks for new releases
|
|
5074
5806
|
ghfind --compare <repo1> <repo2> Compare two+ repos side-by-side
|
|
5807
|
+
ghfind org <name> --json Org profile: repos, stars, top languages
|
|
5075
5808
|
ghfind pkg <query> --json Search npm packages, output JSON
|
|
5076
5809
|
ghfind pkg <query> Search npm packages, text list
|
|
5077
5810
|
ghfind --watch <query> Watch mode (poll every Ns)
|
|
5078
5811
|
ghfind init Run setup wizard
|
|
5812
|
+
ghfind login Import the gh CLI token or paste one
|
|
5079
5813
|
ghfind --completion <shell> Print completion script (bash|zsh|fish)
|
|
5080
5814
|
ghfind --version Print version
|
|
5081
5815
|
ghfind --doctor Run environment diagnostics (troubleshooting)
|
|
@@ -5103,7 +5837,7 @@ Options:
|
|
|
5103
5837
|
const shell = flags.completion;
|
|
5104
5838
|
try {
|
|
5105
5839
|
const content = readFileSync6(
|
|
5106
|
-
join6(
|
|
5840
|
+
join6(PKG_DIR3, "completions", `ghfind.${shell}`),
|
|
5107
5841
|
"utf-8"
|
|
5108
5842
|
);
|
|
5109
5843
|
console.log(content);
|
|
@@ -5117,6 +5851,10 @@ Options:
|
|
|
5117
5851
|
await runInitWizard();
|
|
5118
5852
|
return;
|
|
5119
5853
|
}
|
|
5854
|
+
if (flags.login) {
|
|
5855
|
+
await runLoginWizard();
|
|
5856
|
+
return;
|
|
5857
|
+
}
|
|
5120
5858
|
if (flags.pkg) {
|
|
5121
5859
|
const interactive = !flags.query && !flags.json && !flags.csv && !flags.markdown && !flags.count && !flags.format;
|
|
5122
5860
|
if (interactive) {
|
|
@@ -5127,6 +5865,10 @@ Options:
|
|
|
5127
5865
|
return;
|
|
5128
5866
|
}
|
|
5129
5867
|
const outputFormat = flags.json ? "json" : flags.csv ? "csv" : flags.markdown ? "markdown" : void 0;
|
|
5868
|
+
if (flags.org !== void 0) {
|
|
5869
|
+
await runOrgProfile(flags, outputFormat);
|
|
5870
|
+
return;
|
|
5871
|
+
}
|
|
5130
5872
|
const isNonInteractive = outputFormat || flags.count || flags.format || flags.pipe || flags.watch || flags.releases;
|
|
5131
5873
|
if (isNonInteractive) {
|
|
5132
5874
|
if (flags.releases) {
|
|
@@ -5146,6 +5888,28 @@ function buildSearchContext(flags) {
|
|
|
5146
5888
|
provider: createGitHubSearch(void 0, flags.token ? [flags.token] : [])
|
|
5147
5889
|
};
|
|
5148
5890
|
}
|
|
5891
|
+
async function runOrgProfile(flags, outputFormat) {
|
|
5892
|
+
try {
|
|
5893
|
+
const profile = await fetchOrgProfile(flags.org, {
|
|
5894
|
+
token: flags.token,
|
|
5895
|
+
limit: flags.limit
|
|
5896
|
+
});
|
|
5897
|
+
if (outputFormat === "json") {
|
|
5898
|
+
console.log(formatOrgJson(profile));
|
|
5899
|
+
} else if (outputFormat === "csv") {
|
|
5900
|
+
console.log(formatOrgCsv(profile));
|
|
5901
|
+
} else if (outputFormat === "markdown") {
|
|
5902
|
+
console.log(formatOrgMarkdown(profile));
|
|
5903
|
+
} else if (flags.count) {
|
|
5904
|
+
console.log(profile.publicRepoCount);
|
|
5905
|
+
} else {
|
|
5906
|
+
console.log(formatOrgText(profile));
|
|
5907
|
+
}
|
|
5908
|
+
} catch (err) {
|
|
5909
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
5910
|
+
process.exit(1);
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5149
5913
|
async function runPackageSearch(flags) {
|
|
5150
5914
|
if (flags.registry !== "npm") {
|
|
5151
5915
|
console.error(`Registry "${flags.registry}" not yet supported (only npm).`);
|
|
@@ -5206,22 +5970,32 @@ async function runReleases(flags, outputFormat) {
|
|
|
5206
5970
|
const results2 = await checkAndNotify({ token: flags.token });
|
|
5207
5971
|
const releases = allCachedReleases();
|
|
5208
5972
|
if (outputFormat === "json") {
|
|
5209
|
-
console.log(
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5973
|
+
console.log(
|
|
5974
|
+
JSON.stringify(
|
|
5975
|
+
releases.map((r) => ({
|
|
5976
|
+
fullName: r.fullName,
|
|
5977
|
+
tag: r.tagName,
|
|
5978
|
+
name: r.name,
|
|
5979
|
+
url: r.url,
|
|
5980
|
+
publishedAt: r.publishedAt,
|
|
5981
|
+
prerelease: r.prerelease
|
|
5982
|
+
})),
|
|
5983
|
+
null,
|
|
5984
|
+
2
|
|
5985
|
+
)
|
|
5986
|
+
);
|
|
5217
5987
|
return;
|
|
5218
5988
|
}
|
|
5219
5989
|
const unseen = results2.reduce((n, r) => n + r.newReleases.length, 0);
|
|
5220
5990
|
const errors = results2.filter((r) => r.error).length;
|
|
5221
|
-
console.log(
|
|
5991
|
+
console.log(
|
|
5992
|
+
`Checked ${results2.length} bookmarked repos \u2014 ${unseen} new release${unseen === 1 ? "" : "s"}${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}.`
|
|
5993
|
+
);
|
|
5222
5994
|
for (const r of releases) {
|
|
5223
|
-
console.log(
|
|
5224
|
-
${r.
|
|
5995
|
+
console.log(
|
|
5996
|
+
`${r.fullName} ${r.prerelease ? "[pre] " : ""}${r.tagName} ${r.publishedAt.slice(0, 10)}
|
|
5997
|
+
${r.url}`
|
|
5998
|
+
);
|
|
5225
5999
|
}
|
|
5226
6000
|
}
|
|
5227
6001
|
async function runNonInteractive(flags, outputFormat) {
|
|
@@ -5342,7 +6116,9 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
5342
6116
|
}
|
|
5343
6117
|
main().catch((err) => {
|
|
5344
6118
|
if (err instanceof Error && err.name === "SearchCliError") {
|
|
5345
|
-
console.error(
|
|
6119
|
+
console.error(
|
|
6120
|
+
err instanceof SearchCliError ? err.userMessage : err.message
|
|
6121
|
+
);
|
|
5346
6122
|
} else {
|
|
5347
6123
|
console.error(err instanceof Error ? err.message : String(err));
|
|
5348
6124
|
reportError(err);
|