github-search-cli 9.1.0 → 9.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/bin/ghfind.js +3 -3
- package/completions/ghfind.bash +5 -1
- package/completions/ghfind.fish +2 -0
- package/completions/ghfind.zsh +2 -0
- package/demo.svg +51 -46
- package/dist/{chunk-TUMYR2IC.js → chunk-IVUU2KGR.js} +1 -2
- package/dist/cli.js +1014 -238
- package/dist/{config-DFL44OVL.js → config-GNZOQRTH.js} +1 -1
- package/package.json +2 -4
- package/scripts/postinstall.js +10 -7
- package/src/cli.ts +116 -7
- package/src/config.ts +1 -1
- package/src/help.ts +45 -15
- package/src/init.ts +1 -1
- package/src/package-browser.ts +305 -0
- package/src/package.ts +137 -0
- package/src/search.ts +1 -0
- package/src/themes.ts +60 -36
- package/src/trending.ts +31 -5
- package/src/tui.ts +390 -52
- package/src/types.ts +16 -2
- package/vendor/bun.exe +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import {
|
|
3
2
|
loadConfig,
|
|
4
3
|
saveConfig,
|
|
5
4
|
stateDir
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IVUU2KGR.js";
|
|
7
6
|
|
|
8
7
|
// src/errors.ts
|
|
9
8
|
var SearchCliError = class extends Error {
|
|
@@ -491,7 +490,8 @@ var SearchModule = class _SearchModule {
|
|
|
491
490
|
query.raw,
|
|
492
491
|
options.sort,
|
|
493
492
|
String(options.limit),
|
|
494
|
-
String(options.page ?? 1)
|
|
493
|
+
String(options.page ?? 1),
|
|
494
|
+
options.trendingSince ?? "none"
|
|
495
495
|
);
|
|
496
496
|
const cached = _SearchModule.cache.get(cacheKey);
|
|
497
497
|
if (cached) {
|
|
@@ -513,8 +513,101 @@ function createTrendingSearch(logger2 = noopLogger) {
|
|
|
513
513
|
return new SearchModule(new TrendingAdapter(logger2), logger2);
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
-
// src/
|
|
517
|
-
|
|
516
|
+
// src/package.ts
|
|
517
|
+
function normalizeNpmObject(obj) {
|
|
518
|
+
const p = obj.package;
|
|
519
|
+
const name = p.name ?? "";
|
|
520
|
+
return {
|
|
521
|
+
name,
|
|
522
|
+
version: p.version ?? "",
|
|
523
|
+
description: p.description ?? null,
|
|
524
|
+
url: p.links?.npm ?? `https://www.npmjs.com/package/${name}`,
|
|
525
|
+
downloads: obj.downloads?.monthly ?? 0,
|
|
526
|
+
score: obj.score?.final ?? obj.searchScore ?? 0,
|
|
527
|
+
publisher: p.publisher?.username ?? p.author?.name ?? null,
|
|
528
|
+
keywords: p.keywords ?? [],
|
|
529
|
+
registry: "npm"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function createPackageSearch() {
|
|
533
|
+
return {
|
|
534
|
+
async searchPackage(rawQuery, limit) {
|
|
535
|
+
const size = Math.min(Math.max(Math.trunc(limit) || 50, 1), 250);
|
|
536
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(rawQuery)}&size=${size}`;
|
|
537
|
+
const res = await fetch(url, {
|
|
538
|
+
headers: { "User-Agent": "ghfind", Accept: "application/json" }
|
|
539
|
+
});
|
|
540
|
+
if (!res.ok) {
|
|
541
|
+
throw new Error(`npm search failed: HTTP ${res.status}`);
|
|
542
|
+
}
|
|
543
|
+
const data = await res.json();
|
|
544
|
+
const packages = (data.objects ?? []).map(normalizeNpmObject);
|
|
545
|
+
return { totalCount: data.total ?? packages.length, packages };
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
var PACKAGE_SORT_MODES = [
|
|
550
|
+
{ key: "best-match", label: "best-match" },
|
|
551
|
+
{ key: "downloads", label: "downloads" },
|
|
552
|
+
{ key: "score", label: "score" },
|
|
553
|
+
{ key: "name", label: "name" }
|
|
554
|
+
];
|
|
555
|
+
function sortPackages(pkgs, sort) {
|
|
556
|
+
if (sort === "best-match" || pkgs.length < 2) return pkgs;
|
|
557
|
+
const arr = [...pkgs];
|
|
558
|
+
if (sort === "score") {
|
|
559
|
+
arr.sort((a, b) => b.score - a.score);
|
|
560
|
+
} else if (sort === "downloads") {
|
|
561
|
+
arr.sort((a, b) => b.downloads - a.downloads);
|
|
562
|
+
} else {
|
|
563
|
+
arr.sort((a, b) => a.name.localeCompare(b.name));
|
|
564
|
+
}
|
|
565
|
+
return arr;
|
|
566
|
+
}
|
|
567
|
+
function csvEscape(val) {
|
|
568
|
+
const str = String(val ?? "");
|
|
569
|
+
if (str.includes('"') || str.includes(",") || str.includes("\n")) {
|
|
570
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
571
|
+
}
|
|
572
|
+
return str;
|
|
573
|
+
}
|
|
574
|
+
function formatPackagesJson(pkgs) {
|
|
575
|
+
return JSON.stringify(pkgs, null, 2);
|
|
576
|
+
}
|
|
577
|
+
function formatPackagesCsv(pkgs) {
|
|
578
|
+
const header = "rank,name,version,downloads,score,url";
|
|
579
|
+
const rows = pkgs.map(
|
|
580
|
+
(p, i) => `${i + 1},${csvEscape(p.name)},${p.version},${p.downloads},${p.score.toFixed(2)},${csvEscape(p.url)}`
|
|
581
|
+
);
|
|
582
|
+
return [header, ...rows].join("\n");
|
|
583
|
+
}
|
|
584
|
+
function formatPackagesMarkdown(pkgs) {
|
|
585
|
+
const header = "| # | Package | Version | Downloads | Score |";
|
|
586
|
+
const sep = "|---|---------|---------|-----------|-------|";
|
|
587
|
+
const rows = pkgs.map(
|
|
588
|
+
(p, i) => `| ${i + 1} | [${p.name}@${p.version}](${p.url}) | ${p.version} | ${p.downloads.toLocaleString()} | ${p.score.toFixed(2)} |`
|
|
589
|
+
);
|
|
590
|
+
return [header, sep, ...rows].join("\n");
|
|
591
|
+
}
|
|
592
|
+
function formatPackagesUrls(pkgs) {
|
|
593
|
+
return pkgs.map((p) => p.url).join("\n");
|
|
594
|
+
}
|
|
595
|
+
function formatPackagesNames(pkgs) {
|
|
596
|
+
return pkgs.map((p) => p.name).join("\n");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/package-browser.ts
|
|
600
|
+
import {
|
|
601
|
+
createCliRenderer,
|
|
602
|
+
BoxRenderable,
|
|
603
|
+
TextRenderable,
|
|
604
|
+
ScrollBoxRenderable,
|
|
605
|
+
StyledText,
|
|
606
|
+
bold,
|
|
607
|
+
fg,
|
|
608
|
+
dim,
|
|
609
|
+
t
|
|
610
|
+
} from "@opentui/core";
|
|
518
611
|
|
|
519
612
|
// src/open-url.ts
|
|
520
613
|
function openUrl(url) {
|
|
@@ -527,7 +620,282 @@ function openUrl(url) {
|
|
|
527
620
|
}
|
|
528
621
|
}
|
|
529
622
|
|
|
623
|
+
// src/package-browser.ts
|
|
624
|
+
var C = {
|
|
625
|
+
bg: "#1a1b26",
|
|
626
|
+
surface: "#16161e",
|
|
627
|
+
text: "#a9b1d6",
|
|
628
|
+
muted: "#565f89",
|
|
629
|
+
cyan: "#7dcfff",
|
|
630
|
+
purple: "#bb9af7",
|
|
631
|
+
green: "#9ece6a",
|
|
632
|
+
gold: "#e0af68",
|
|
633
|
+
orange: "#ff9e64",
|
|
634
|
+
red: "#f7768e",
|
|
635
|
+
blue: "#7aa2f7",
|
|
636
|
+
lightBlue: "#b4f9f8",
|
|
637
|
+
selectionBg: "#3b6ce8",
|
|
638
|
+
selectionText: "#c0caf5",
|
|
639
|
+
border: "#363b54",
|
|
640
|
+
descText: "#6c7086",
|
|
641
|
+
// lighter muted for descriptions
|
|
642
|
+
nameText: "#c0caf5"
|
|
643
|
+
// bright for package names
|
|
644
|
+
};
|
|
645
|
+
var INT_FMT = new Intl.NumberFormat("en-US");
|
|
646
|
+
function fmtCount(n) {
|
|
647
|
+
return INT_FMT.format(Math.trunc(n));
|
|
648
|
+
}
|
|
649
|
+
function formatPackageLine(pack) {
|
|
650
|
+
const nameStr = `${pack.name}@${pack.version}`.padEnd(28).slice(0, 28);
|
|
651
|
+
const dlStr = `\u2193 ${fmtCount(pack.downloads)}`.padEnd(20).slice(0, 20);
|
|
652
|
+
const scoreStr = `score ${pack.score.toFixed(2)}`.padEnd(12).slice(0, 12);
|
|
653
|
+
const desc = (pack.description ?? "").length > 60 ? (pack.description ?? "").slice(0, 57) + "..." : pack.description ?? "";
|
|
654
|
+
const line1 = t`${bold(fg(C.nameText)(nameStr))}${fg(C.cyan)(dlStr)}${fg(C.gold)(scoreStr)}`;
|
|
655
|
+
const line2 = t`${dim(fg(C.descText)(` ${desc}`))}`;
|
|
656
|
+
const newline = { text: "\n", __isChunk: true };
|
|
657
|
+
const chunks = [...line1.chunks, newline, ...line2.chunks];
|
|
658
|
+
return new StyledText(chunks);
|
|
659
|
+
}
|
|
660
|
+
async function launchPackageBrowser() {
|
|
661
|
+
let renderer;
|
|
662
|
+
try {
|
|
663
|
+
renderer = await createCliRenderer();
|
|
664
|
+
} catch (err) {
|
|
665
|
+
console.error(
|
|
666
|
+
"Failed to start the interactive browser.\n",
|
|
667
|
+
`Reason: ${err instanceof Error ? err.message : String(err)}
|
|
668
|
+
`
|
|
669
|
+
);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const root = renderer.root;
|
|
674
|
+
root.flexDirection = "column";
|
|
675
|
+
let query = "";
|
|
676
|
+
let dirty = false;
|
|
677
|
+
let packages = [];
|
|
678
|
+
let selectedIdx = 0;
|
|
679
|
+
let isLoading = false;
|
|
680
|
+
const outerBox = new BoxRenderable(renderer, {
|
|
681
|
+
flexGrow: 1,
|
|
682
|
+
flexDirection: "column",
|
|
683
|
+
backgroundColor: C.bg,
|
|
684
|
+
border: true,
|
|
685
|
+
borderColor: C.border,
|
|
686
|
+
borderStyle: "rounded",
|
|
687
|
+
paddingX: 0,
|
|
688
|
+
paddingY: 0
|
|
689
|
+
});
|
|
690
|
+
const headerBox = new BoxRenderable(renderer, {
|
|
691
|
+
height: 1,
|
|
692
|
+
backgroundColor: C.bg,
|
|
693
|
+
paddingX: 1
|
|
694
|
+
});
|
|
695
|
+
const headerText = new TextRenderable(renderer, {
|
|
696
|
+
content: t`${bold(fg(C.purple)("\u27E0 tulipsearch"))}${fg(C.muted)(" \u2014 Packages")}`,
|
|
697
|
+
fg: C.purple,
|
|
698
|
+
bg: C.bg,
|
|
699
|
+
height: 1
|
|
700
|
+
});
|
|
701
|
+
headerBox.add(headerText);
|
|
702
|
+
outerBox.add(headerBox);
|
|
703
|
+
const inputBox = new BoxRenderable(renderer, {
|
|
704
|
+
height: 1,
|
|
705
|
+
flexDirection: "row",
|
|
706
|
+
backgroundColor: C.surface,
|
|
707
|
+
paddingX: 1
|
|
708
|
+
});
|
|
709
|
+
let inputText = null;
|
|
710
|
+
function renderInput() {
|
|
711
|
+
if (inputText) inputBox.remove(inputText);
|
|
712
|
+
const cursor = isLoading ? " " : "\u258A";
|
|
713
|
+
inputText = new TextRenderable(renderer, {
|
|
714
|
+
content: t`${fg(C.cyan)("\u276F ")}${query}${dim(fg(C.muted)(cursor))}`,
|
|
715
|
+
fg: C.text,
|
|
716
|
+
bg: C.surface,
|
|
717
|
+
height: 1
|
|
718
|
+
});
|
|
719
|
+
inputBox.add(inputText);
|
|
720
|
+
}
|
|
721
|
+
const statusBox = new BoxRenderable(renderer, {
|
|
722
|
+
height: 1,
|
|
723
|
+
backgroundColor: C.bg,
|
|
724
|
+
paddingX: 1
|
|
725
|
+
});
|
|
726
|
+
let statusText = null;
|
|
727
|
+
function setStatus(cfg) {
|
|
728
|
+
if (statusText) statusBox.remove(statusText);
|
|
729
|
+
statusText = new TextRenderable(renderer, {
|
|
730
|
+
content: cfg.content,
|
|
731
|
+
fg: cfg.color ?? C.text,
|
|
732
|
+
bg: C.bg,
|
|
733
|
+
height: 1
|
|
734
|
+
});
|
|
735
|
+
statusBox.add(statusText);
|
|
736
|
+
}
|
|
737
|
+
const scrollBox = new ScrollBoxRenderable(renderer, {
|
|
738
|
+
flexGrow: 1,
|
|
739
|
+
backgroundColor: C.bg,
|
|
740
|
+
scrollY: true,
|
|
741
|
+
scrollX: false,
|
|
742
|
+
viewportCulling: true
|
|
743
|
+
});
|
|
744
|
+
const rowBoxes = [];
|
|
745
|
+
const rowTexts = [];
|
|
746
|
+
function clearRows() {
|
|
747
|
+
for (const rb of rowBoxes) scrollBox.remove(rb);
|
|
748
|
+
rowBoxes.length = 0;
|
|
749
|
+
rowTexts.length = 0;
|
|
750
|
+
}
|
|
751
|
+
function rebuildList() {
|
|
752
|
+
clearRows();
|
|
753
|
+
packages.forEach((p, i) => {
|
|
754
|
+
const isEven = i % 2 === 0;
|
|
755
|
+
const isSelected = i === selectedIdx;
|
|
756
|
+
const rowBg = isSelected ? C.selectionBg : isEven ? C.bg : C.surface;
|
|
757
|
+
const rowText = new TextRenderable(renderer, {
|
|
758
|
+
content: formatPackageLine(p),
|
|
759
|
+
bg: rowBg,
|
|
760
|
+
height: 2
|
|
761
|
+
});
|
|
762
|
+
const rowBox = new BoxRenderable(renderer, {
|
|
763
|
+
flexDirection: "row",
|
|
764
|
+
backgroundColor: rowBg
|
|
765
|
+
});
|
|
766
|
+
rowBox.add(rowText);
|
|
767
|
+
rowBoxes.push(rowBox);
|
|
768
|
+
rowTexts.push(rowText);
|
|
769
|
+
scrollBox.add(rowBox);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const footerBox = new BoxRenderable(renderer, {
|
|
773
|
+
height: 1,
|
|
774
|
+
backgroundColor: C.bg,
|
|
775
|
+
paddingX: 1
|
|
776
|
+
});
|
|
777
|
+
const footerText = new TextRenderable(renderer, {
|
|
778
|
+
content: " type query \u21B5 search \u2191\u2193 navigate \u21B5 open q/esc quit",
|
|
779
|
+
fg: C.muted,
|
|
780
|
+
bg: C.bg,
|
|
781
|
+
height: 1
|
|
782
|
+
});
|
|
783
|
+
footerBox.add(footerText);
|
|
784
|
+
outerBox.add(footerBox);
|
|
785
|
+
root.add(outerBox);
|
|
786
|
+
outerBox.add(inputBox);
|
|
787
|
+
outerBox.add(statusBox);
|
|
788
|
+
const divider = new TextRenderable(renderer, {
|
|
789
|
+
content: "\u2500".repeat(80),
|
|
790
|
+
fg: C.border,
|
|
791
|
+
bg: C.bg,
|
|
792
|
+
height: 1
|
|
793
|
+
});
|
|
794
|
+
outerBox.add(divider);
|
|
795
|
+
outerBox.add(scrollBox);
|
|
796
|
+
async function runSearch() {
|
|
797
|
+
const q = query.trim();
|
|
798
|
+
if (!q) return;
|
|
799
|
+
isLoading = true;
|
|
800
|
+
dirty = false;
|
|
801
|
+
clearRows();
|
|
802
|
+
setStatus({ content: t`${dim(fg(C.muted)(` Loading packages for "${q}"...`))}` });
|
|
803
|
+
renderer.requestRender();
|
|
804
|
+
try {
|
|
805
|
+
const searcher = createPackageSearch();
|
|
806
|
+
const res = await searcher.searchPackage(q, 25);
|
|
807
|
+
packages = res.packages;
|
|
808
|
+
selectedIdx = 0;
|
|
809
|
+
rebuildList();
|
|
810
|
+
setStatus({
|
|
811
|
+
content: t`${fg(C.muted)(` ${packages.length} of ${fmtCount(res.totalCount)} results for `)}${fg(C.cyan)(`"${q}"`)}`
|
|
812
|
+
});
|
|
813
|
+
} catch (err) {
|
|
814
|
+
packages = [];
|
|
815
|
+
clearRows();
|
|
816
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
817
|
+
setStatus({
|
|
818
|
+
content: t`${fg(C.red)(` Error: ${msg.slice(0, 70)} \u2014 press \u21B5 to retry`)}`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
isLoading = false;
|
|
822
|
+
renderInput();
|
|
823
|
+
renderer.requestRender();
|
|
824
|
+
}
|
|
825
|
+
function openSelectedPackage() {
|
|
826
|
+
if (selectedIdx < 0 || selectedIdx >= packages.length) return;
|
|
827
|
+
const p = packages[selectedIdx];
|
|
828
|
+
openUrl(p.url);
|
|
829
|
+
}
|
|
830
|
+
function moveSelection(delta) {
|
|
831
|
+
const oldIdx = selectedIdx;
|
|
832
|
+
const newIdx = Math.max(0, Math.min(packages.length - 1, oldIdx + delta));
|
|
833
|
+
if (newIdx === oldIdx) return;
|
|
834
|
+
selectedIdx = newIdx;
|
|
835
|
+
const setBg = (idx, selected) => {
|
|
836
|
+
if (idx < 0 || idx >= rowBoxes.length) return;
|
|
837
|
+
const bg = selected ? C.selectionBg : idx % 2 === 0 ? C.bg : C.surface;
|
|
838
|
+
rowBoxes[idx].backgroundColor = bg;
|
|
839
|
+
if (rowTexts[idx]) rowTexts[idx].bg = bg;
|
|
840
|
+
};
|
|
841
|
+
setBg(oldIdx, false);
|
|
842
|
+
setBg(newIdx, true);
|
|
843
|
+
renderer.requestRender();
|
|
844
|
+
}
|
|
845
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
846
|
+
const name = key.name || "";
|
|
847
|
+
if (name === "q" || name === "escape") {
|
|
848
|
+
process.exit(0);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (name === "backspace") {
|
|
852
|
+
query = query.slice(0, -1);
|
|
853
|
+
dirty = true;
|
|
854
|
+
renderInput();
|
|
855
|
+
renderer.requestRender();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (name === "space") {
|
|
859
|
+
query += " ";
|
|
860
|
+
dirty = true;
|
|
861
|
+
renderInput();
|
|
862
|
+
renderer.requestRender();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (name.length === 1) {
|
|
866
|
+
query += name;
|
|
867
|
+
dirty = true;
|
|
868
|
+
renderInput();
|
|
869
|
+
renderer.requestRender();
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (name === "up" || name === "k") {
|
|
873
|
+
moveSelection(-1);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (name === "down" || name === "j") {
|
|
877
|
+
moveSelection(1);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (name === "enter" || name === "return") {
|
|
881
|
+
if (dirty || packages.length === 0) {
|
|
882
|
+
runSearch();
|
|
883
|
+
} else {
|
|
884
|
+
openSelectedPackage();
|
|
885
|
+
}
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
renderer.start();
|
|
890
|
+
renderInput();
|
|
891
|
+
setStatus({
|
|
892
|
+
content: t`${fg(C.muted)(" Type a package query above and press \u21B5 to search")}`
|
|
893
|
+
});
|
|
894
|
+
renderer.requestRender();
|
|
895
|
+
}
|
|
896
|
+
|
|
530
897
|
// src/output.ts
|
|
898
|
+
import { writeFileSync } from "fs";
|
|
531
899
|
var FORMATS = /* @__PURE__ */ new Map([
|
|
532
900
|
// Export formats
|
|
533
901
|
["json", formatJson],
|
|
@@ -544,7 +912,7 @@ var FORMATS = /* @__PURE__ */ new Map([
|
|
|
544
912
|
function formatJson(repos) {
|
|
545
913
|
return JSON.stringify(repos, null, 2);
|
|
546
914
|
}
|
|
547
|
-
function
|
|
915
|
+
function csvEscape2(val) {
|
|
548
916
|
const str = String(val ?? "");
|
|
549
917
|
if (str.includes('"') || str.includes(",") || str.includes("\n")) {
|
|
550
918
|
return `"${str.replace(/"/g, '""')}"`;
|
|
@@ -554,7 +922,7 @@ function csvEscape(val) {
|
|
|
554
922
|
function formatCsv(repos) {
|
|
555
923
|
const header = "rank,full_name,stars,forks,language,url";
|
|
556
924
|
const rows = repos.map(
|
|
557
|
-
(r, i) => `${i + 1},${
|
|
925
|
+
(r, i) => `${i + 1},${csvEscape2(r.fullName)},${r.stars},${r.forks},${csvEscape2(r.language ?? "")},${csvEscape2(r.url)}`
|
|
558
926
|
);
|
|
559
927
|
return [header, ...rows].join("\n");
|
|
560
928
|
}
|
|
@@ -664,36 +1032,42 @@ import { createInterface } from "readline";
|
|
|
664
1032
|
|
|
665
1033
|
// src/themes.ts
|
|
666
1034
|
var THEMES = {
|
|
667
|
-
"premium-dark": {
|
|
668
|
-
name: "premium-dark",
|
|
669
|
-
bg: "#3D3B3B",
|
|
670
|
-
surface: "#4a4848",
|
|
671
|
-
surfaceAlt: "#525050",
|
|
672
|
-
text: "#e0e4f0",
|
|
673
|
-
muted: "#a8a6a6",
|
|
674
|
-
blue: "#89b4fa",
|
|
675
|
-
green: "#a6e3a1",
|
|
676
|
-
yellow: "#f9e2af",
|
|
677
|
-
red: "#f38ba8",
|
|
678
|
-
teal: "#94e2d5",
|
|
679
|
-
purple: "#cba6f7",
|
|
680
|
-
orange: "#fab387",
|
|
681
|
-
border: "#1C1C1C",
|
|
682
|
-
borderAlt: "#2a2a2a",
|
|
683
|
-
separator: "#1C1C1C",
|
|
684
|
-
selectionBg: "#2A2A9C",
|
|
685
|
-
selectionText: "#ffffff",
|
|
686
|
-
success: "#a6e3a1",
|
|
687
|
-
warning: "#f9e2af",
|
|
688
|
-
error: "#f38ba8"
|
|
689
|
-
},
|
|
690
1035
|
"tokyo-night": {
|
|
691
1036
|
name: "tokyo-night",
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1037
|
+
// Genuine Tokyo Night palette — cool blue-violet mood
|
|
1038
|
+
bg: "#1a1b26",
|
|
1039
|
+
surface: "#24283b",
|
|
1040
|
+
surfaceAlt: "#292e42",
|
|
1041
|
+
text: "#c0caf5",
|
|
1042
|
+
muted: "#565f89",
|
|
1043
|
+
blue: "#7aa2f7",
|
|
1044
|
+
green: "#9ece6a",
|
|
1045
|
+
yellow: "#e0af68",
|
|
1046
|
+
red: "#f7768e",
|
|
1047
|
+
teal: "#73daca",
|
|
1048
|
+
purple: "#bb9af7",
|
|
1049
|
+
orange: "#ff9e64",
|
|
1050
|
+
border: "#24283b",
|
|
1051
|
+
borderAlt: "#1f2335",
|
|
1052
|
+
separator: "#1f2335",
|
|
1053
|
+
selectionBg: "#2d3f76",
|
|
1054
|
+
selectionText: "#c0caf5",
|
|
1055
|
+
success: "#9ece6a",
|
|
1056
|
+
warning: "#e0af68",
|
|
1057
|
+
error: "#f7768e",
|
|
1058
|
+
surfaceDim: "#16161e",
|
|
1059
|
+
accent: "#7aa2f7",
|
|
1060
|
+
accentDim: "#5d7abf",
|
|
1061
|
+
borderAccent: "#1f2335"
|
|
1062
|
+
},
|
|
1063
|
+
"premium-dark": {
|
|
1064
|
+
name: "premium-dark",
|
|
1065
|
+
// Warm counter-mood to Tokyo Night — amber/rust accent
|
|
1066
|
+
bg: "#1e1e2e",
|
|
1067
|
+
surface: "#262637",
|
|
1068
|
+
surfaceAlt: "#2e2e42",
|
|
1069
|
+
text: "#cdd6f4",
|
|
1070
|
+
muted: "#6c7086",
|
|
697
1071
|
blue: "#89b4fa",
|
|
698
1072
|
green: "#a6e3a1",
|
|
699
1073
|
yellow: "#f9e2af",
|
|
@@ -701,60 +1075,18 @@ var THEMES = {
|
|
|
701
1075
|
teal: "#94e2d5",
|
|
702
1076
|
purple: "#cba6f7",
|
|
703
1077
|
orange: "#fab387",
|
|
704
|
-
border: "#
|
|
705
|
-
borderAlt: "#
|
|
706
|
-
separator: "#
|
|
707
|
-
selectionBg: "#
|
|
708
|
-
selectionText: "#
|
|
1078
|
+
border: "#313244",
|
|
1079
|
+
borderAlt: "#2a2a3d",
|
|
1080
|
+
separator: "#2a2a3d",
|
|
1081
|
+
selectionBg: "#45475a",
|
|
1082
|
+
selectionText: "#cdd6f4",
|
|
709
1083
|
success: "#a6e3a1",
|
|
710
1084
|
warning: "#f9e2af",
|
|
711
|
-
error: "#f38ba8"
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
surface: "#4a4848",
|
|
717
|
-
surfaceAlt: "#525050",
|
|
718
|
-
text: "#f8f8f2",
|
|
719
|
-
muted: "#a8a6a6",
|
|
720
|
-
blue: "#8be9fd",
|
|
721
|
-
green: "#50fa7b",
|
|
722
|
-
yellow: "#f1fa8c",
|
|
723
|
-
red: "#ff5555",
|
|
724
|
-
teal: "#50fa7b",
|
|
725
|
-
purple: "#bd93f9",
|
|
726
|
-
orange: "#ffb86c",
|
|
727
|
-
border: "#1C1C1C",
|
|
728
|
-
borderAlt: "#2a2a2a",
|
|
729
|
-
separator: "#1C1C1C",
|
|
730
|
-
selectionBg: "#2A2A9C",
|
|
731
|
-
selectionText: "#ffffff",
|
|
732
|
-
success: "#50fa7b",
|
|
733
|
-
warning: "#f1fa8c",
|
|
734
|
-
error: "#ff5555"
|
|
735
|
-
},
|
|
736
|
-
monokai: {
|
|
737
|
-
name: "monokai",
|
|
738
|
-
bg: "#3D3B3B",
|
|
739
|
-
surface: "#4a4848",
|
|
740
|
-
surfaceAlt: "#525050",
|
|
741
|
-
text: "#f8f8f2",
|
|
742
|
-
muted: "#a8a6a6",
|
|
743
|
-
blue: "#66d9ef",
|
|
744
|
-
green: "#a6e22e",
|
|
745
|
-
yellow: "#e6db74",
|
|
746
|
-
red: "#f92672",
|
|
747
|
-
teal: "#a6e22e",
|
|
748
|
-
purple: "#ae81ff",
|
|
749
|
-
orange: "#fd971f",
|
|
750
|
-
border: "#1C1C1C",
|
|
751
|
-
borderAlt: "#2a2a2a",
|
|
752
|
-
separator: "#1C1C1C",
|
|
753
|
-
selectionBg: "#2A2A9C",
|
|
754
|
-
selectionText: "#f8f8f2",
|
|
755
|
-
success: "#a6e22e",
|
|
756
|
-
warning: "#e6db74",
|
|
757
|
-
error: "#f92672"
|
|
1085
|
+
error: "#f38ba8",
|
|
1086
|
+
surfaceDim: "#181825",
|
|
1087
|
+
accent: "#f9e2af",
|
|
1088
|
+
accentDim: "#c9a87a",
|
|
1089
|
+
borderAccent: "#313244"
|
|
758
1090
|
}
|
|
759
1091
|
};
|
|
760
1092
|
function loadTheme(name) {
|
|
@@ -790,18 +1122,46 @@ async function runInitWizard() {
|
|
|
790
1122
|
const config = {
|
|
791
1123
|
defaultSort: ["best-match", "stars", "updated", "forks"].includes(sortStr) ? sortStr : "best-match",
|
|
792
1124
|
defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr) || 50)),
|
|
793
|
-
theme: themes.includes(themeStr) ? themeStr :
|
|
1125
|
+
theme: themes.includes(themeStr) ? themeStr : themes[0],
|
|
794
1126
|
cacheTtlSeconds: 300,
|
|
795
1127
|
defaultTab: "search"
|
|
796
1128
|
};
|
|
797
1129
|
if (token) config.githubToken = token;
|
|
798
1130
|
saveConfig(config);
|
|
799
|
-
const { configPath } = await import("./config-
|
|
1131
|
+
const { configPath } = await import("./config-GNZOQRTH.js");
|
|
800
1132
|
console.log(` \u2713 Config saved to ${configPath()}`);
|
|
801
1133
|
console.log(" Run 'ghfind' to start browsing!");
|
|
802
1134
|
console.log("");
|
|
803
1135
|
}
|
|
804
1136
|
|
|
1137
|
+
// src/compare.ts
|
|
1138
|
+
function buildComparisonTable(repos) {
|
|
1139
|
+
if (repos.length < 2) return "Select at least 2 repos to compare (press c to add).";
|
|
1140
|
+
const rows = [];
|
|
1141
|
+
const names = repos.map((r) => r.fullName);
|
|
1142
|
+
rows.push({ label: "Stars", values: repos.map((r) => `\u2605 ${r.stars.toLocaleString()}`) });
|
|
1143
|
+
rows.push({ label: "Forks", values: repos.map((r) => r.forks.toLocaleString()) });
|
|
1144
|
+
rows.push({ label: "Language", values: repos.map((r) => r.language ?? "\u2014") });
|
|
1145
|
+
rows.push({ label: "Created", values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "\u2014") });
|
|
1146
|
+
rows.push({ label: "Updated", values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "\u2014") });
|
|
1147
|
+
rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 3).join(", ") ?? "\u2014") || "\u2014") });
|
|
1148
|
+
const colW = Math.max(15, ...names.map((n) => n.length));
|
|
1149
|
+
const valW = Math.max(8, ...rows.flatMap((r) => r.values.map((v) => v.length)));
|
|
1150
|
+
const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
|
|
1151
|
+
const sep = ["".padEnd(14, "\u2500"), ...names.map(() => "".padEnd(colW, "\u2500"))];
|
|
1152
|
+
const lines = [
|
|
1153
|
+
"\u250C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u252C") + "\u2510",
|
|
1154
|
+
"\u2502" + header.map((h) => ` ${h} `).join("\u2502") + "\u2502",
|
|
1155
|
+
"\u251C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u253C") + "\u2524"
|
|
1156
|
+
];
|
|
1157
|
+
for (const row of rows) {
|
|
1158
|
+
const cells = [row.label.padEnd(14), ...row.values.map((v) => v.padEnd(colW))];
|
|
1159
|
+
lines.push("\u2502" + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
|
|
1160
|
+
}
|
|
1161
|
+
lines.push("\u2514" + sep.map((s) => "\u2500" + s + "\u2500").join("\u2534") + "\u2518");
|
|
1162
|
+
return lines.join("\n");
|
|
1163
|
+
}
|
|
1164
|
+
|
|
805
1165
|
// src/cli.ts
|
|
806
1166
|
import { readFileSync as readFileSync3 } from "fs";
|
|
807
1167
|
import { join as join3, dirname as dirname2 } from "path";
|
|
@@ -809,25 +1169,25 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
809
1169
|
|
|
810
1170
|
// src/tui.ts
|
|
811
1171
|
import {
|
|
812
|
-
createCliRenderer as
|
|
813
|
-
BoxRenderable as
|
|
814
|
-
TextRenderable as
|
|
1172
|
+
createCliRenderer as createCliRenderer3,
|
|
1173
|
+
BoxRenderable as BoxRenderable3,
|
|
1174
|
+
TextRenderable as TextRenderable3,
|
|
815
1175
|
InputRenderable,
|
|
816
1176
|
SelectRenderable,
|
|
817
|
-
ScrollBoxRenderable as
|
|
1177
|
+
ScrollBoxRenderable as ScrollBoxRenderable3
|
|
818
1178
|
} from "@opentui/core";
|
|
819
1179
|
|
|
820
1180
|
// src/trending.ts
|
|
821
1181
|
import {
|
|
822
|
-
createCliRenderer,
|
|
823
|
-
BoxRenderable,
|
|
824
|
-
TextRenderable,
|
|
825
|
-
ScrollBoxRenderable,
|
|
826
|
-
StyledText,
|
|
827
|
-
bold,
|
|
828
|
-
fg,
|
|
829
|
-
dim,
|
|
830
|
-
t
|
|
1182
|
+
createCliRenderer as createCliRenderer2,
|
|
1183
|
+
BoxRenderable as BoxRenderable2,
|
|
1184
|
+
TextRenderable as TextRenderable2,
|
|
1185
|
+
ScrollBoxRenderable as ScrollBoxRenderable2,
|
|
1186
|
+
StyledText as StyledText2,
|
|
1187
|
+
bold as bold2,
|
|
1188
|
+
fg as fg2,
|
|
1189
|
+
dim as dim2,
|
|
1190
|
+
t as t2
|
|
831
1191
|
} from "@opentui/core";
|
|
832
1192
|
var TAB_NAMES = ["Today", "This Week", "This Month"];
|
|
833
1193
|
function tabSince(tab) {
|
|
@@ -853,6 +1213,16 @@ if (import.meta.main && false) {
|
|
|
853
1213
|
// src/help.ts
|
|
854
1214
|
function buildHelpSections() {
|
|
855
1215
|
return [
|
|
1216
|
+
{
|
|
1217
|
+
title: " Landing ",
|
|
1218
|
+
titleColor: "blue",
|
|
1219
|
+
rows: [
|
|
1220
|
+
{ keys: "\u2191 \u2193 / j k", action: "Navigate menu cards" },
|
|
1221
|
+
{ keys: "Enter", action: "Select mode" },
|
|
1222
|
+
{ keys: "? / h", action: "Toggle this help screen" },
|
|
1223
|
+
{ keys: "q", action: "Quit" }
|
|
1224
|
+
]
|
|
1225
|
+
},
|
|
856
1226
|
{
|
|
857
1227
|
title: " Navigation ",
|
|
858
1228
|
titleColor: "blue",
|
|
@@ -882,6 +1252,17 @@ function buildHelpSections() {
|
|
|
882
1252
|
{ keys: "\u2190 \u2192 / h l", action: "Navigate tabs" }
|
|
883
1253
|
]
|
|
884
1254
|
},
|
|
1255
|
+
{
|
|
1256
|
+
title: " Packages Mode ",
|
|
1257
|
+
titleColor: "cyan",
|
|
1258
|
+
rows: [
|
|
1259
|
+
{ keys: "/", action: "Search npm packages" },
|
|
1260
|
+
{ keys: "\u2191 \u2193 / j k", action: "Move selection" },
|
|
1261
|
+
{ keys: "Enter", action: "View package details" },
|
|
1262
|
+
{ keys: "Space", action: "Open command menu" }
|
|
1263
|
+
],
|
|
1264
|
+
note: "Search by name, description, or tags. Sort by downloads or score."
|
|
1265
|
+
},
|
|
885
1266
|
{
|
|
886
1267
|
title: " Command Menu (Space)",
|
|
887
1268
|
titleColor: "purple",
|
|
@@ -894,9 +1275,17 @@ function buildHelpSections() {
|
|
|
894
1275
|
{ keys: "Deep-dive", action: "Languages + contributors + excerpt" },
|
|
895
1276
|
{ keys: "Bookmark", action: "Save / unsave selected repo" },
|
|
896
1277
|
{ keys: "Compare", action: "Add to / remove from comparison" },
|
|
897
|
-
{ keys: "Trending", action: "Switch to trending browser" }
|
|
1278
|
+
{ keys: "Trending", action: "Switch to trending browser" },
|
|
1279
|
+
{ keys: "Packages", action: "Switch to npm packages" },
|
|
1280
|
+
{ keys: "Export", action: "Export results to file" },
|
|
1281
|
+
{ keys: "Share", action: "Share repo as text" },
|
|
1282
|
+
{ keys: "Topics", action: "Browse GitHub topics" },
|
|
1283
|
+
{ keys: "Saved", action: "View saved searches" },
|
|
1284
|
+
{ keys: "Notifs", action: "Check GitHub notifications" },
|
|
1285
|
+
{ keys: "History", action: "View search history" },
|
|
1286
|
+
{ keys: "Help", action: "Show this help screen" }
|
|
898
1287
|
],
|
|
899
|
-
note: "
|
|
1288
|
+
note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc"
|
|
900
1289
|
},
|
|
901
1290
|
{
|
|
902
1291
|
title: " Overlay Keys ",
|
|
@@ -905,7 +1294,8 @@ function buildHelpSections() {
|
|
|
905
1294
|
{ keys: "Esc / q", action: "Close any overlay" },
|
|
906
1295
|
{ keys: "Enter", action: "Confirm selection / run action" },
|
|
907
1296
|
{ keys: "d", action: "Delete entry (history, bookmarks, saved)" },
|
|
908
|
-
{ keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" }
|
|
1297
|
+
{ keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" },
|
|
1298
|
+
{ keys: "\u2190 \u2192 / h l", action: "Navigate tabs / panels" }
|
|
909
1299
|
]
|
|
910
1300
|
},
|
|
911
1301
|
{
|
|
@@ -915,23 +1305,26 @@ function buildHelpSections() {
|
|
|
915
1305
|
{ keys: "keywords", action: "Free-text search" },
|
|
916
1306
|
{ keys: "key:value", action: "Qualifier filter" },
|
|
917
1307
|
{ keys: "-key:value", action: "Negated qualifier" },
|
|
918
|
-
{ keys: '"phrase"', action: "Exact phrase match" }
|
|
1308
|
+
{ keys: '"phrase"', action: "Exact phrase match" },
|
|
1309
|
+
{ keys: "language:TypeScript", action: "Filter by language" },
|
|
1310
|
+
{ keys: "stars:>1000", action: "Minimum stars" },
|
|
1311
|
+
{ keys: "fork:true", action: "Include forks" },
|
|
1312
|
+
{ keys: "user:octocat", action: "User's repos" },
|
|
1313
|
+
{ keys: "org:facebook", action: "Organization repos" },
|
|
1314
|
+
{ keys: "repo:facebook/react", action: "Specific repo" },
|
|
1315
|
+
{ keys: "topic:react", action: "Filter by topic" },
|
|
1316
|
+
{ keys: "license:mit", action: "License type" },
|
|
1317
|
+
{ keys: "pushed:>2024-01-01", action: "Last push date" },
|
|
1318
|
+
{ keys: "archived:false", action: "Exclude archived" }
|
|
919
1319
|
]
|
|
920
1320
|
},
|
|
921
1321
|
{
|
|
922
|
-
title: "
|
|
1322
|
+
title: " Utility Keys ",
|
|
923
1323
|
titleColor: "muted",
|
|
924
1324
|
rows: [
|
|
925
|
-
{ keys: "
|
|
926
|
-
{ keys: "
|
|
927
|
-
{ keys: "
|
|
928
|
-
{ keys: "user:", action: "User's repos" },
|
|
929
|
-
{ keys: "org:", action: "Organization repos" },
|
|
930
|
-
{ keys: "repo:", action: "Specific repo" },
|
|
931
|
-
{ keys: "topic:", action: "Filter by topic" },
|
|
932
|
-
{ keys: "license:", action: "License type" },
|
|
933
|
-
{ keys: "pushed:>YYYY-MM-DD", action: "Last push date" },
|
|
934
|
-
{ keys: "archived:false", action: "Exclude archived" }
|
|
1325
|
+
{ keys: "t", action: "Cycle theme (light / dark / solarized)" },
|
|
1326
|
+
{ keys: "Ctrl+X", action: "Copy repo URL to clipboard" },
|
|
1327
|
+
{ keys: "Ctrl+C", action: "Copy search query to clipboard" }
|
|
935
1328
|
]
|
|
936
1329
|
},
|
|
937
1330
|
{
|
|
@@ -1182,34 +1575,6 @@ function buildDeepDiveText(data) {
|
|
|
1182
1575
|
].join("\n");
|
|
1183
1576
|
}
|
|
1184
1577
|
|
|
1185
|
-
// src/compare.ts
|
|
1186
|
-
function buildComparisonTable(repos) {
|
|
1187
|
-
if (repos.length < 2) return "Select at least 2 repos to compare (press c to add).";
|
|
1188
|
-
const rows = [];
|
|
1189
|
-
const names = repos.map((r) => r.fullName);
|
|
1190
|
-
rows.push({ label: "Stars", values: repos.map((r) => `\u2605 ${r.stars.toLocaleString()}`) });
|
|
1191
|
-
rows.push({ label: "Forks", values: repos.map((r) => r.forks.toLocaleString()) });
|
|
1192
|
-
rows.push({ label: "Language", values: repos.map((r) => r.language ?? "\u2014") });
|
|
1193
|
-
rows.push({ label: "Created", values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "\u2014") });
|
|
1194
|
-
rows.push({ label: "Updated", values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "\u2014") });
|
|
1195
|
-
rows.push({ label: "Topics", values: repos.map((r) => (r.topics?.slice(0, 3).join(", ") ?? "\u2014") || "\u2014") });
|
|
1196
|
-
const colW = Math.max(15, ...names.map((n) => n.length));
|
|
1197
|
-
const valW = Math.max(8, ...rows.flatMap((r) => r.values.map((v) => v.length)));
|
|
1198
|
-
const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
|
|
1199
|
-
const sep = ["".padEnd(14, "\u2500"), ...names.map(() => "".padEnd(colW, "\u2500"))];
|
|
1200
|
-
const lines = [
|
|
1201
|
-
"\u250C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u252C") + "\u2510",
|
|
1202
|
-
"\u2502" + header.map((h) => ` ${h} `).join("\u2502") + "\u2502",
|
|
1203
|
-
"\u251C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u253C") + "\u2524"
|
|
1204
|
-
];
|
|
1205
|
-
for (const row of rows) {
|
|
1206
|
-
const cells = [row.label.padEnd(14), ...row.values.map((v) => v.padEnd(colW))];
|
|
1207
|
-
lines.push("\u2502" + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
|
|
1208
|
-
}
|
|
1209
|
-
lines.push("\u2514" + sep.map((s) => "\u2500" + s + "\u2500").join("\u2534") + "\u2518");
|
|
1210
|
-
return lines.join("\n");
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
1578
|
// src/explore.ts
|
|
1214
1579
|
async function fetchTopics() {
|
|
1215
1580
|
const res = await fetch("https://api.github.com/search/topics?q=popular&per_page=30", {
|
|
@@ -1465,7 +1830,6 @@ var colors = {
|
|
|
1465
1830
|
surfaceAlt: "#525050",
|
|
1466
1831
|
text: "#e0e4f0",
|
|
1467
1832
|
muted: "#a8a6a6",
|
|
1468
|
-
blue: "#89b4fa",
|
|
1469
1833
|
green: "#a6e3a1",
|
|
1470
1834
|
yellow: "#f9e2af",
|
|
1471
1835
|
red: "#f38ba8",
|
|
@@ -1500,10 +1864,20 @@ var SORT_MODES = [
|
|
|
1500
1864
|
{ key: "updated", label: "updated" },
|
|
1501
1865
|
{ key: "forks", label: "forks" }
|
|
1502
1866
|
];
|
|
1503
|
-
async function launchBrowser() {
|
|
1867
|
+
async function launchBrowser(theme_override) {
|
|
1504
1868
|
let renderer;
|
|
1505
1869
|
try {
|
|
1506
|
-
|
|
1870
|
+
const isTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
1871
|
+
if (isTTY) {
|
|
1872
|
+
renderer = await createCliRenderer3();
|
|
1873
|
+
} else {
|
|
1874
|
+
renderer = await createCliRenderer3({
|
|
1875
|
+
width: 80,
|
|
1876
|
+
height: 24,
|
|
1877
|
+
screenMode: "main-screen",
|
|
1878
|
+
autoFocus: false
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1507
1881
|
} catch (err) {
|
|
1508
1882
|
const isNode = process.versions.bun === void 0;
|
|
1509
1883
|
if (isNode) {
|
|
@@ -1526,10 +1900,16 @@ async function launchBrowser() {
|
|
|
1526
1900
|
}
|
|
1527
1901
|
const root = renderer.root;
|
|
1528
1902
|
root.flexDirection = "column";
|
|
1903
|
+
root.border = true;
|
|
1904
|
+
root.borderTop = true;
|
|
1905
|
+
root.borderBottom = true;
|
|
1906
|
+
root.borderLeft = true;
|
|
1907
|
+
root.borderRight = true;
|
|
1908
|
+
root.borderColor = colors.borderAccent;
|
|
1529
1909
|
root.backgroundColor = colors.bg;
|
|
1530
1910
|
const config = loadConfig();
|
|
1531
1911
|
const githubToken = config.githubToken || process.env.GITHUB_TOKEN;
|
|
1532
|
-
const theme = loadTheme(config.theme);
|
|
1912
|
+
const theme = loadTheme(theme_override || config.theme);
|
|
1533
1913
|
Object.assign(colors, theme);
|
|
1534
1914
|
const session = restoreSession();
|
|
1535
1915
|
let currentRepos = [];
|
|
@@ -1538,6 +1918,9 @@ async function launchBrowser() {
|
|
|
1538
1918
|
let currentQueryInput = session?.query ?? "";
|
|
1539
1919
|
let isLoading = false;
|
|
1540
1920
|
let currentMode = session?.mode ?? "search";
|
|
1921
|
+
let packages = [];
|
|
1922
|
+
let currentPackageSort = "best-match";
|
|
1923
|
+
let packageResultsRaw = [];
|
|
1541
1924
|
let trendingTab = session?.trendingTab ?? "This Week";
|
|
1542
1925
|
let trendingPeriod = "this week";
|
|
1543
1926
|
let graphFullscreen = false;
|
|
@@ -1548,7 +1931,7 @@ async function launchBrowser() {
|
|
|
1548
1931
|
let deepDiveActive = false;
|
|
1549
1932
|
let compareList = [];
|
|
1550
1933
|
let quitArmed = false;
|
|
1551
|
-
const header = new
|
|
1934
|
+
const header = new TextRenderable3(renderer, {
|
|
1552
1935
|
content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
|
|
1553
1936
|
backgroundColor: colors.bg,
|
|
1554
1937
|
color: colors.muted,
|
|
@@ -1556,7 +1939,7 @@ async function launchBrowser() {
|
|
|
1556
1939
|
borderBottom: true,
|
|
1557
1940
|
borderBottomColor: colors.border
|
|
1558
1941
|
});
|
|
1559
|
-
const trendingTabBox = new
|
|
1942
|
+
const trendingTabBox = new BoxRenderable3(renderer, {
|
|
1560
1943
|
flexDirection: "row",
|
|
1561
1944
|
height: 1,
|
|
1562
1945
|
backgroundColor: colors.surface,
|
|
@@ -1566,12 +1949,12 @@ async function launchBrowser() {
|
|
|
1566
1949
|
});
|
|
1567
1950
|
const trendingTabTexts = [];
|
|
1568
1951
|
function renderTrendingTabs() {
|
|
1569
|
-
for (const
|
|
1952
|
+
for (const t3 of trendingTabTexts) trendingTabBox.remove(t3);
|
|
1570
1953
|
trendingTabTexts.length = 0;
|
|
1571
1954
|
TAB_NAMES.forEach((name, i) => {
|
|
1572
1955
|
const isActive = name === trendingTab;
|
|
1573
1956
|
const label = ` ${name} `;
|
|
1574
|
-
const tt = new
|
|
1957
|
+
const tt = new TextRenderable3(renderer, {
|
|
1575
1958
|
content: label,
|
|
1576
1959
|
color: isActive ? colors.bg : colors.muted,
|
|
1577
1960
|
backgroundColor: isActive ? colors.blue : colors.bg,
|
|
@@ -1580,7 +1963,7 @@ async function launchBrowser() {
|
|
|
1580
1963
|
trendingTabTexts.push(tt);
|
|
1581
1964
|
trendingTabBox.add(tt);
|
|
1582
1965
|
if (i < TAB_NAMES.length - 1) {
|
|
1583
|
-
const sp = new
|
|
1966
|
+
const sp = new TextRenderable3(renderer, {
|
|
1584
1967
|
content: " ",
|
|
1585
1968
|
color: colors.muted,
|
|
1586
1969
|
backgroundColor: colors.bg,
|
|
@@ -1593,16 +1976,20 @@ async function launchBrowser() {
|
|
|
1593
1976
|
}
|
|
1594
1977
|
root.add(header);
|
|
1595
1978
|
root.add(trendingTabBox);
|
|
1596
|
-
const
|
|
1979
|
+
const gapBox = new BoxRenderable3(renderer, {
|
|
1980
|
+
height: 1,
|
|
1981
|
+
visible: true
|
|
1982
|
+
});
|
|
1983
|
+
root.add(gapBox);
|
|
1984
|
+
const searchBox = new BoxRenderable3(renderer, {
|
|
1597
1985
|
visible: true,
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
title: "
|
|
1986
|
+
bordered: true,
|
|
1987
|
+
borderColor: colors.border,
|
|
1988
|
+
title: "",
|
|
1601
1989
|
titleColor: colors.blue,
|
|
1602
1990
|
width: "100%",
|
|
1603
1991
|
flexDirection: "row",
|
|
1604
|
-
|
|
1605
|
-
paddingBottom: 0,
|
|
1992
|
+
marginTop: 1,
|
|
1606
1993
|
backgroundColor: colors.surface
|
|
1607
1994
|
});
|
|
1608
1995
|
const searchInput = new InputRenderable(renderer, {
|
|
@@ -1617,7 +2004,7 @@ async function launchBrowser() {
|
|
|
1617
2004
|
});
|
|
1618
2005
|
searchBox.add(searchInput);
|
|
1619
2006
|
root.add(searchBox);
|
|
1620
|
-
const toolbarText = new
|
|
2007
|
+
const toolbarText = new TextRenderable3(renderer, {
|
|
1621
2008
|
content: formatToolbar(currentSort, currentLimit, totalCount),
|
|
1622
2009
|
visible: true,
|
|
1623
2010
|
// hidden in trending mode
|
|
@@ -1630,15 +2017,16 @@ async function launchBrowser() {
|
|
|
1630
2017
|
paddingX: 1
|
|
1631
2018
|
});
|
|
1632
2019
|
root.add(toolbarText);
|
|
1633
|
-
const body = new
|
|
2020
|
+
const body = new BoxRenderable3(renderer, {
|
|
1634
2021
|
flexDirection: "row",
|
|
1635
2022
|
flexGrow: 1,
|
|
1636
2023
|
gap: 1,
|
|
1637
2024
|
paddingY: 1,
|
|
1638
2025
|
paddingX: 1
|
|
1639
2026
|
});
|
|
1640
|
-
const resultsBox = new
|
|
1641
|
-
|
|
2027
|
+
const resultsBox = new BoxRenderable3(renderer, {
|
|
2028
|
+
bordered: true,
|
|
2029
|
+
borderColor: colors.border,
|
|
1642
2030
|
borderBottomColor: colors.border,
|
|
1643
2031
|
title: " Results ",
|
|
1644
2032
|
titleColor: colors.blue,
|
|
@@ -1666,8 +2054,9 @@ async function launchBrowser() {
|
|
|
1666
2054
|
itemSpacing: 0
|
|
1667
2055
|
});
|
|
1668
2056
|
resultsBox.add(resultsSelect);
|
|
1669
|
-
const detailBox = new
|
|
1670
|
-
|
|
2057
|
+
const detailBox = new BoxRenderable3(renderer, {
|
|
2058
|
+
bordered: true,
|
|
2059
|
+
borderColor: colors.border,
|
|
1671
2060
|
borderBottomColor: colors.border,
|
|
1672
2061
|
title: " Details ",
|
|
1673
2062
|
titleColor: colors.green,
|
|
@@ -1678,16 +2067,148 @@ async function launchBrowser() {
|
|
|
1678
2067
|
focusable: false,
|
|
1679
2068
|
backgroundColor: colors.surface
|
|
1680
2069
|
});
|
|
1681
|
-
const detailText = new
|
|
2070
|
+
const detailText = new TextRenderable3(renderer, {
|
|
1682
2071
|
content: "",
|
|
1683
|
-
color: colors.text
|
|
2072
|
+
color: colors.text,
|
|
2073
|
+
wrapMode: "none"
|
|
1684
2074
|
});
|
|
1685
2075
|
detailBox.add(detailText);
|
|
1686
2076
|
body.add(resultsBox);
|
|
1687
2077
|
body.add(detailBox);
|
|
1688
2078
|
root.add(body);
|
|
2079
|
+
const landingBox = new BoxRenderable3(renderer, {
|
|
2080
|
+
visible: false,
|
|
2081
|
+
flexGrow: 1,
|
|
2082
|
+
flexDirection: "column",
|
|
2083
|
+
alignItems: "center",
|
|
2084
|
+
justifyContent: "center"
|
|
2085
|
+
});
|
|
2086
|
+
root.add(landingBox);
|
|
2087
|
+
const landingBanner = new TextRenderable3(renderer, {
|
|
2088
|
+
content: [
|
|
2089
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
2090
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
2091
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
2092
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
2093
|
+
"\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",
|
|
2094
|
+
" \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"
|
|
2095
|
+
].join("\n"),
|
|
2096
|
+
color: colors.accent,
|
|
2097
|
+
backgroundColor: colors.bg,
|
|
2098
|
+
height: 6
|
|
2099
|
+
});
|
|
2100
|
+
landingBox.add(landingBanner);
|
|
2101
|
+
const landingOptions = [
|
|
2102
|
+
{
|
|
2103
|
+
icon: "\u{1F50D}",
|
|
2104
|
+
title: "Repo Search",
|
|
2105
|
+
desc: "Search GitHub repositories by query, language, stars, and more",
|
|
2106
|
+
action: () => {
|
|
2107
|
+
currentMode = "search";
|
|
2108
|
+
showLanding(false);
|
|
2109
|
+
showSearchMode();
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
icon: "\u{1F4E6}",
|
|
2114
|
+
title: "Package Search",
|
|
2115
|
+
desc: "Search npm packages by name, description, or tags",
|
|
2116
|
+
action: () => {
|
|
2117
|
+
currentMode = "packages";
|
|
2118
|
+
showLanding(false);
|
|
2119
|
+
showPackagesMode();
|
|
2120
|
+
}
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
icon: "\u{1F525}",
|
|
2124
|
+
title: "Trending",
|
|
2125
|
+
desc: "Browse the hottest repos on GitHub right now",
|
|
2126
|
+
action: () => {
|
|
2127
|
+
currentMode = "trending";
|
|
2128
|
+
showLanding(false);
|
|
2129
|
+
loadTrending();
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
];
|
|
2133
|
+
let landingSelected = 0;
|
|
2134
|
+
const landingCards = [];
|
|
2135
|
+
for (let i = 0; i < landingOptions.length; i++) {
|
|
2136
|
+
const opt = landingOptions[i];
|
|
2137
|
+
const card = new BoxRenderable3(renderer, {
|
|
2138
|
+
flexDirection: "column",
|
|
2139
|
+
height: 5,
|
|
2140
|
+
width: "60%",
|
|
2141
|
+
marginTop: 1,
|
|
2142
|
+
border: true,
|
|
2143
|
+
borderColor: i === 0 ? colors.accent : colors.border,
|
|
2144
|
+
paddingX: 1,
|
|
2145
|
+
focusable: false
|
|
2146
|
+
});
|
|
2147
|
+
landingCards.push(card);
|
|
2148
|
+
landingBox.add(card);
|
|
2149
|
+
const spacerTop = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
2150
|
+
const titleLine = new TextRenderable3(renderer, {
|
|
2151
|
+
content: opt.title,
|
|
2152
|
+
color: i === 0 ? colors.accent : colors.text,
|
|
2153
|
+
height: 1
|
|
2154
|
+
});
|
|
2155
|
+
const spacerBot = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
2156
|
+
card.add(spacerTop);
|
|
2157
|
+
card.add(titleLine);
|
|
2158
|
+
card.add(spacerBot);
|
|
2159
|
+
}
|
|
2160
|
+
const landingHint = new TextRenderable3(renderer, {
|
|
2161
|
+
content: " \u2191\u2193 Navigate \u21E9 Select [?]help [q]uit",
|
|
2162
|
+
color: colors.muted,
|
|
2163
|
+
backgroundColor: colors.bg,
|
|
2164
|
+
height: 1,
|
|
2165
|
+
marginTop: 1
|
|
2166
|
+
});
|
|
2167
|
+
landingBox.add(landingHint);
|
|
2168
|
+
function showLanding(visible) {
|
|
2169
|
+
landingBox.visible = visible;
|
|
2170
|
+
if (visible) {
|
|
2171
|
+
currentMode = "landing";
|
|
2172
|
+
hideMainContent();
|
|
2173
|
+
landingSelected = 0;
|
|
2174
|
+
updateLandingCards();
|
|
2175
|
+
renderer.requestRender();
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
function updateLandingCards() {
|
|
2179
|
+
for (let i = 0; i < landingCards.length; i++) {
|
|
2180
|
+
const isSel = i === landingSelected;
|
|
2181
|
+
landingCards[i].borderColor = isSel ? colors.accent : colors.border;
|
|
2182
|
+
const children = landingCards[i].children ?? [];
|
|
2183
|
+
const titleChild = children[1];
|
|
2184
|
+
if (titleChild) {
|
|
2185
|
+
titleChild.color = isSel ? colors.accent : colors.text;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
2190
|
+
if (currentMode === "landing") {
|
|
2191
|
+
if (key.name === "up" || key.name === "k") {
|
|
2192
|
+
landingSelected = Math.max(0, landingSelected - 1);
|
|
2193
|
+
updateLandingCards();
|
|
2194
|
+
renderer.requestRender();
|
|
2195
|
+
} else if (key.name === "down" || key.name === "j") {
|
|
2196
|
+
landingSelected = Math.min(landingOptions.length - 1, landingSelected + 1);
|
|
2197
|
+
updateLandingCards();
|
|
2198
|
+
renderer.requestRender();
|
|
2199
|
+
} else if (key.name === "enter" || key.name === "return") {
|
|
2200
|
+
landingOptions[landingSelected].action();
|
|
2201
|
+
} else if (key.name === "?" || key.name === "h") {
|
|
2202
|
+
showOverlay(currentOverlay === "help" ? "none" : "help");
|
|
2203
|
+
} else if (key.name === "q") {
|
|
2204
|
+
cleanup();
|
|
2205
|
+
}
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
1689
2209
|
function hideMainContent() {
|
|
1690
2210
|
trendingTabBox.visible = false;
|
|
2211
|
+
gapBox.visible = false;
|
|
1691
2212
|
searchBox.visible = false;
|
|
1692
2213
|
toolbarText.visible = false;
|
|
1693
2214
|
body.visible = false;
|
|
@@ -1697,6 +2218,7 @@ async function launchBrowser() {
|
|
|
1697
2218
|
trendingTabBox.visible = true;
|
|
1698
2219
|
} else {
|
|
1699
2220
|
searchBox.visible = true;
|
|
2221
|
+
gapBox.visible = true;
|
|
1700
2222
|
toolbarText.visible = true;
|
|
1701
2223
|
}
|
|
1702
2224
|
body.visible = true;
|
|
@@ -1718,7 +2240,16 @@ async function launchBrowser() {
|
|
|
1718
2240
|
updateDim.visible = false;
|
|
1719
2241
|
if (type === "none") {
|
|
1720
2242
|
currentOverlay = "none";
|
|
1721
|
-
|
|
2243
|
+
if (currentMode === "landing") {
|
|
2244
|
+
landingBox.visible = true;
|
|
2245
|
+
body.visible = false;
|
|
2246
|
+
trendingTabBox.visible = false;
|
|
2247
|
+
gapBox.visible = false;
|
|
2248
|
+
searchBox.visible = false;
|
|
2249
|
+
toolbarText.visible = false;
|
|
2250
|
+
} else {
|
|
2251
|
+
showMainContent();
|
|
2252
|
+
}
|
|
1722
2253
|
renderer.requestRender();
|
|
1723
2254
|
return;
|
|
1724
2255
|
}
|
|
@@ -1757,14 +2288,14 @@ async function launchBrowser() {
|
|
|
1757
2288
|
renderer.requestRender();
|
|
1758
2289
|
}
|
|
1759
2290
|
const helpSections = buildHelpSections();
|
|
1760
|
-
const helpBox = new
|
|
2291
|
+
const helpBox = new BoxRenderable3(renderer, {
|
|
1761
2292
|
visible: false,
|
|
1762
2293
|
flexGrow: 1,
|
|
1763
2294
|
backgroundColor: colors.bg,
|
|
1764
2295
|
borderBottom: true,
|
|
1765
2296
|
borderBottomColor: colors.border
|
|
1766
2297
|
});
|
|
1767
|
-
const helpScroll = new
|
|
2298
|
+
const helpScroll = new ScrollBoxRenderable3(renderer, {
|
|
1768
2299
|
flexGrow: 1,
|
|
1769
2300
|
backgroundColor: colors.bg,
|
|
1770
2301
|
scrollY: true,
|
|
@@ -1779,7 +2310,7 @@ async function launchBrowser() {
|
|
|
1779
2310
|
}
|
|
1780
2311
|
});
|
|
1781
2312
|
for (const section of helpSections) {
|
|
1782
|
-
const sectionBox = new
|
|
2313
|
+
const sectionBox = new BoxRenderable3(renderer, {
|
|
1783
2314
|
borderBottom: true,
|
|
1784
2315
|
borderBottomColor: colors.border,
|
|
1785
2316
|
title: section.title,
|
|
@@ -1793,7 +2324,7 @@ async function launchBrowser() {
|
|
|
1793
2324
|
for (const row of section.rows) {
|
|
1794
2325
|
const keys = row.keys.padEnd(HELP_KEYS_COLUMN);
|
|
1795
2326
|
sectionBox.add(
|
|
1796
|
-
new
|
|
2327
|
+
new TextRenderable3(renderer, {
|
|
1797
2328
|
content: ` ${keys} ${row.action}`,
|
|
1798
2329
|
color: colors.text
|
|
1799
2330
|
})
|
|
@@ -1801,7 +2332,7 @@ async function launchBrowser() {
|
|
|
1801
2332
|
}
|
|
1802
2333
|
if (section.note) {
|
|
1803
2334
|
sectionBox.add(
|
|
1804
|
-
new
|
|
2335
|
+
new TextRenderable3(renderer, {
|
|
1805
2336
|
content: ` ${section.note}`,
|
|
1806
2337
|
color: colors.muted
|
|
1807
2338
|
})
|
|
@@ -1811,7 +2342,7 @@ async function launchBrowser() {
|
|
|
1811
2342
|
}
|
|
1812
2343
|
helpBox.add(helpScroll);
|
|
1813
2344
|
root.add(helpBox);
|
|
1814
|
-
const historyBox = new
|
|
2345
|
+
const historyBox = new BoxRenderable3(renderer, {
|
|
1815
2346
|
visible: false,
|
|
1816
2347
|
flexGrow: 1,
|
|
1817
2348
|
backgroundColor: colors.bg,
|
|
@@ -1848,7 +2379,7 @@ async function launchBrowser() {
|
|
|
1848
2379
|
}
|
|
1849
2380
|
historySelect.setSelectedIndex(0);
|
|
1850
2381
|
}
|
|
1851
|
-
const bookmarksBox = new
|
|
2382
|
+
const bookmarksBox = new BoxRenderable3(renderer, {
|
|
1852
2383
|
visible: false,
|
|
1853
2384
|
flexGrow: 1,
|
|
1854
2385
|
backgroundColor: colors.bg,
|
|
@@ -1885,7 +2416,7 @@ async function launchBrowser() {
|
|
|
1885
2416
|
}
|
|
1886
2417
|
bookmarksSelect.setSelectedIndex(0);
|
|
1887
2418
|
}
|
|
1888
|
-
const savedBox = new
|
|
2419
|
+
const savedBox = new BoxRenderable3(renderer, {
|
|
1889
2420
|
visible: false,
|
|
1890
2421
|
flexGrow: 1,
|
|
1891
2422
|
backgroundColor: colors.bg,
|
|
@@ -1907,7 +2438,7 @@ async function launchBrowser() {
|
|
|
1907
2438
|
});
|
|
1908
2439
|
savedBox.add(savedSelect);
|
|
1909
2440
|
root.add(savedBox);
|
|
1910
|
-
const topicsBox = new
|
|
2441
|
+
const topicsBox = new BoxRenderable3(renderer, {
|
|
1911
2442
|
visible: false,
|
|
1912
2443
|
flexGrow: 1,
|
|
1913
2444
|
backgroundColor: colors.bg,
|
|
@@ -1932,10 +2463,10 @@ async function launchBrowser() {
|
|
|
1932
2463
|
async function refreshTopics() {
|
|
1933
2464
|
try {
|
|
1934
2465
|
const topics = await fetchTopics();
|
|
1935
|
-
topicsSelect.options = topics.map((
|
|
1936
|
-
name: `${
|
|
1937
|
-
description:
|
|
1938
|
-
value:
|
|
2466
|
+
topicsSelect.options = topics.map((t3) => ({
|
|
2467
|
+
name: `${t3.name} (${t3.repoCount.toLocaleString()} repos)`,
|
|
2468
|
+
description: t3.description.slice(0, 80),
|
|
2469
|
+
value: t3
|
|
1939
2470
|
}));
|
|
1940
2471
|
} catch {
|
|
1941
2472
|
topicsSelect.options = [
|
|
@@ -1944,7 +2475,7 @@ async function launchBrowser() {
|
|
|
1944
2475
|
}
|
|
1945
2476
|
topicsSelect.setSelectedIndex(0);
|
|
1946
2477
|
}
|
|
1947
|
-
const exportBox = new
|
|
2478
|
+
const exportBox = new BoxRenderable3(renderer, {
|
|
1948
2479
|
visible: false,
|
|
1949
2480
|
flexGrow: 1,
|
|
1950
2481
|
backgroundColor: colors.bg,
|
|
@@ -1983,7 +2514,7 @@ async function launchBrowser() {
|
|
|
1983
2514
|
});
|
|
1984
2515
|
exportBox.add(exportSelect);
|
|
1985
2516
|
root.add(exportBox);
|
|
1986
|
-
const compareBox = new
|
|
2517
|
+
const compareBox = new BoxRenderable3(renderer, {
|
|
1987
2518
|
visible: false,
|
|
1988
2519
|
flexGrow: 1,
|
|
1989
2520
|
backgroundColor: colors.bg,
|
|
@@ -1992,14 +2523,14 @@ async function launchBrowser() {
|
|
|
1992
2523
|
title: " Comparison ",
|
|
1993
2524
|
titleColor: colors.yellow
|
|
1994
2525
|
});
|
|
1995
|
-
const compareText = new
|
|
2526
|
+
const compareText = new TextRenderable3(renderer, {
|
|
1996
2527
|
content: "",
|
|
1997
2528
|
color: colors.text,
|
|
1998
2529
|
backgroundColor: colors.bg
|
|
1999
2530
|
});
|
|
2000
2531
|
compareBox.add(compareText);
|
|
2001
2532
|
root.add(compareBox);
|
|
2002
|
-
const notifsBox = new
|
|
2533
|
+
const notifsBox = new BoxRenderable3(renderer, {
|
|
2003
2534
|
visible: false,
|
|
2004
2535
|
flexGrow: 1,
|
|
2005
2536
|
backgroundColor: colors.bg,
|
|
@@ -2008,7 +2539,7 @@ async function launchBrowser() {
|
|
|
2008
2539
|
title: " Notifications ",
|
|
2009
2540
|
titleColor: colors.yellow
|
|
2010
2541
|
});
|
|
2011
|
-
const notifsText = new
|
|
2542
|
+
const notifsText = new TextRenderable3(renderer, {
|
|
2012
2543
|
content: "(no notifications)",
|
|
2013
2544
|
color: colors.text,
|
|
2014
2545
|
backgroundColor: colors.bg
|
|
@@ -2023,7 +2554,7 @@ async function launchBrowser() {
|
|
|
2023
2554
|
notifsText.content = notifs2.map((n) => ` ${n.icon} ${n.message}`).join("\n");
|
|
2024
2555
|
}
|
|
2025
2556
|
}
|
|
2026
|
-
const shareBox = new
|
|
2557
|
+
const shareBox = new BoxRenderable3(renderer, {
|
|
2027
2558
|
visible: false,
|
|
2028
2559
|
flexGrow: 1,
|
|
2029
2560
|
backgroundColor: colors.bg,
|
|
@@ -2084,7 +2615,7 @@ async function launchBrowser() {
|
|
|
2084
2615
|
}
|
|
2085
2616
|
savedSelect.setSelectedIndex(0);
|
|
2086
2617
|
}
|
|
2087
|
-
const leaderDim = new
|
|
2618
|
+
const leaderDim = new BoxRenderable3(renderer, {
|
|
2088
2619
|
visible: false,
|
|
2089
2620
|
position: "absolute",
|
|
2090
2621
|
top: 0,
|
|
@@ -2095,7 +2626,7 @@ async function launchBrowser() {
|
|
|
2095
2626
|
// 60% opacity black (hex alpha: 0x99 ≈ 153/255)
|
|
2096
2627
|
});
|
|
2097
2628
|
root.add(leaderDim);
|
|
2098
|
-
const leaderBox = new
|
|
2629
|
+
const leaderBox = new BoxRenderable3(renderer, {
|
|
2099
2630
|
visible: false,
|
|
2100
2631
|
position: "absolute",
|
|
2101
2632
|
width: "80%",
|
|
@@ -2127,7 +2658,7 @@ async function launchBrowser() {
|
|
|
2127
2658
|
selectedDescriptionColor: colors.selectionText,
|
|
2128
2659
|
itemSpacing: 0
|
|
2129
2660
|
});
|
|
2130
|
-
const leaderFooter = new
|
|
2661
|
+
const leaderFooter = new TextRenderable3(renderer, {
|
|
2131
2662
|
content: " \u2191\u2193 select Enter run Esc/q close ",
|
|
2132
2663
|
color: colors.muted,
|
|
2133
2664
|
backgroundColor: colors.surfaceDim,
|
|
@@ -2140,7 +2671,7 @@ async function launchBrowser() {
|
|
|
2140
2671
|
leaderBox.add(leaderFooter);
|
|
2141
2672
|
root.add(leaderDim);
|
|
2142
2673
|
root.add(leaderBox);
|
|
2143
|
-
const updateDim = new
|
|
2674
|
+
const updateDim = new BoxRenderable3(renderer, {
|
|
2144
2675
|
visible: false,
|
|
2145
2676
|
position: "absolute",
|
|
2146
2677
|
top: 0,
|
|
@@ -2150,7 +2681,7 @@ async function launchBrowser() {
|
|
|
2150
2681
|
backgroundColor: "#00000088"
|
|
2151
2682
|
});
|
|
2152
2683
|
root.add(updateDim);
|
|
2153
|
-
const updateBox = new
|
|
2684
|
+
const updateBox = new BoxRenderable3(renderer, {
|
|
2154
2685
|
visible: false,
|
|
2155
2686
|
position: "absolute",
|
|
2156
2687
|
width: "55%",
|
|
@@ -2169,20 +2700,20 @@ async function launchBrowser() {
|
|
|
2169
2700
|
paddingLeft: 2,
|
|
2170
2701
|
paddingRight: 2
|
|
2171
2702
|
});
|
|
2172
|
-
const updateText = new
|
|
2703
|
+
const updateText = new TextRenderable3(renderer, {
|
|
2173
2704
|
content: "",
|
|
2174
2705
|
color: colors.accent,
|
|
2175
2706
|
backgroundColor: colors.surfaceDim
|
|
2176
2707
|
});
|
|
2177
2708
|
let updateSelectedOption = 0;
|
|
2178
|
-
const updateOptionsRow = new
|
|
2709
|
+
const updateOptionsRow = new BoxRenderable3(renderer, {
|
|
2179
2710
|
flexDirection: "row",
|
|
2180
2711
|
gap: 1,
|
|
2181
2712
|
backgroundColor: colors.surfaceDim,
|
|
2182
2713
|
height: 3,
|
|
2183
2714
|
width: "100%"
|
|
2184
2715
|
});
|
|
2185
|
-
const updateNowBox = new
|
|
2716
|
+
const updateNowBox = new BoxRenderable3(renderer, {
|
|
2186
2717
|
border: true,
|
|
2187
2718
|
borderColor: updateSelectedOption === 0 ? colors.yellow : colors.border,
|
|
2188
2719
|
backgroundColor: colors.surfaceDim,
|
|
@@ -2191,13 +2722,13 @@ async function launchBrowser() {
|
|
|
2191
2722
|
paddingLeft: 1,
|
|
2192
2723
|
paddingRight: 1
|
|
2193
2724
|
});
|
|
2194
|
-
const updateNowText = new
|
|
2725
|
+
const updateNowText = new TextRenderable3(renderer, {
|
|
2195
2726
|
content: " Update Now ",
|
|
2196
2727
|
color: updateSelectedOption === 0 ? colors.yellow : colors.text,
|
|
2197
2728
|
backgroundColor: colors.surfaceDim
|
|
2198
2729
|
});
|
|
2199
2730
|
updateNowBox.add(updateNowText);
|
|
2200
|
-
const laterBox = new
|
|
2731
|
+
const laterBox = new BoxRenderable3(renderer, {
|
|
2201
2732
|
border: true,
|
|
2202
2733
|
borderColor: updateSelectedOption === 1 ? colors.yellow : colors.border,
|
|
2203
2734
|
backgroundColor: colors.surfaceDim,
|
|
@@ -2206,13 +2737,13 @@ async function launchBrowser() {
|
|
|
2206
2737
|
paddingLeft: 1,
|
|
2207
2738
|
paddingRight: 1
|
|
2208
2739
|
});
|
|
2209
|
-
const laterText = new
|
|
2740
|
+
const laterText = new TextRenderable3(renderer, {
|
|
2210
2741
|
content: " Later",
|
|
2211
2742
|
color: updateSelectedOption === 1 ? colors.yellow : colors.text,
|
|
2212
2743
|
backgroundColor: colors.surfaceDim
|
|
2213
2744
|
});
|
|
2214
2745
|
laterBox.add(laterText);
|
|
2215
|
-
const neverBox = new
|
|
2746
|
+
const neverBox = new BoxRenderable3(renderer, {
|
|
2216
2747
|
border: true,
|
|
2217
2748
|
borderColor: updateSelectedOption === 2 ? colors.yellow : colors.border,
|
|
2218
2749
|
backgroundColor: colors.surfaceDim,
|
|
@@ -2221,7 +2752,7 @@ async function launchBrowser() {
|
|
|
2221
2752
|
paddingLeft: 1,
|
|
2222
2753
|
paddingRight: 1
|
|
2223
2754
|
});
|
|
2224
|
-
const neverText = new
|
|
2755
|
+
const neverText = new TextRenderable3(renderer, {
|
|
2225
2756
|
content: " Don't show again",
|
|
2226
2757
|
color: updateSelectedOption === 2 ? colors.yellow : colors.text,
|
|
2227
2758
|
backgroundColor: colors.surfaceDim
|
|
@@ -2238,7 +2769,7 @@ async function launchBrowser() {
|
|
|
2238
2769
|
}
|
|
2239
2770
|
updateOptionsRow.add(updateNowBox);
|
|
2240
2771
|
updateOptionsRow.add(laterBox);
|
|
2241
|
-
const updateFooter = new
|
|
2772
|
+
const updateFooter = new TextRenderable3(renderer, {
|
|
2242
2773
|
content: " \u2190\u2192 select Enter Esc/q close",
|
|
2243
2774
|
color: colors.muted,
|
|
2244
2775
|
backgroundColor: colors.surfaceDim,
|
|
@@ -2331,6 +2862,29 @@ async function launchBrowser() {
|
|
|
2331
2862
|
}
|
|
2332
2863
|
}
|
|
2333
2864
|
);
|
|
2865
|
+
} else if (currentMode === "packages") {
|
|
2866
|
+
searchItems.push(
|
|
2867
|
+
{
|
|
2868
|
+
type: "action",
|
|
2869
|
+
name: "Sort",
|
|
2870
|
+
description: `Cycle package sort (current: ${currentPackageSort})`,
|
|
2871
|
+
action: () => changePackageSort()
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
type: "action",
|
|
2875
|
+
name: "Limit",
|
|
2876
|
+
description: `Cycle result limit (current: ${currentLimit})`,
|
|
2877
|
+
action: () => changeLimit()
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
type: "action",
|
|
2881
|
+
name: "Refresh",
|
|
2882
|
+
description: "Re-run current package search",
|
|
2883
|
+
action: () => {
|
|
2884
|
+
if (currentQueryInput) doPackageSearch(currentQueryInput);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
);
|
|
2334
2888
|
}
|
|
2335
2889
|
if (searchItems.length > 0) {
|
|
2336
2890
|
groups.push({
|
|
@@ -2407,6 +2961,15 @@ async function launchBrowser() {
|
|
|
2407
2961
|
});
|
|
2408
2962
|
}
|
|
2409
2963
|
const navItems = [];
|
|
2964
|
+
navItems.push({
|
|
2965
|
+
type: "action",
|
|
2966
|
+
name: "Packages",
|
|
2967
|
+
description: "Search npm packages",
|
|
2968
|
+
action: () => {
|
|
2969
|
+
showOverlay("none");
|
|
2970
|
+
showPackagesMode();
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2410
2973
|
if (currentMode === "search") {
|
|
2411
2974
|
navItems.push({
|
|
2412
2975
|
type: "action",
|
|
@@ -2634,7 +3197,7 @@ async function launchBrowser() {
|
|
|
2634
3197
|
renderer.requestRender();
|
|
2635
3198
|
}
|
|
2636
3199
|
}
|
|
2637
|
-
const readmeBox = new
|
|
3200
|
+
const readmeBox = new BoxRenderable3(renderer, {
|
|
2638
3201
|
visible: false,
|
|
2639
3202
|
flexGrow: 1,
|
|
2640
3203
|
backgroundColor: colors.surface,
|
|
@@ -2643,7 +3206,7 @@ async function launchBrowser() {
|
|
|
2643
3206
|
title: " README ",
|
|
2644
3207
|
titleColor: colors.green
|
|
2645
3208
|
});
|
|
2646
|
-
const readmeScroll = new
|
|
3209
|
+
const readmeScroll = new ScrollBoxRenderable3(renderer, {
|
|
2647
3210
|
flexGrow: 1,
|
|
2648
3211
|
backgroundColor: colors.bg,
|
|
2649
3212
|
scrollY: true,
|
|
@@ -2657,14 +3220,14 @@ async function launchBrowser() {
|
|
|
2657
3220
|
width: 1
|
|
2658
3221
|
}
|
|
2659
3222
|
});
|
|
2660
|
-
const readmeText = new
|
|
3223
|
+
const readmeText = new TextRenderable3(renderer, {
|
|
2661
3224
|
content: "",
|
|
2662
3225
|
color: colors.text,
|
|
2663
3226
|
backgroundColor: colors.bg
|
|
2664
3227
|
});
|
|
2665
3228
|
readmeScroll.add(readmeText);
|
|
2666
3229
|
readmeBox.add(readmeScroll);
|
|
2667
|
-
const readmeFooter = new
|
|
3230
|
+
const readmeFooter = new TextRenderable3(renderer, {
|
|
2668
3231
|
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
2669
3232
|
color: colors.muted,
|
|
2670
3233
|
backgroundColor: colors.bg,
|
|
@@ -2710,7 +3273,7 @@ async function launchBrowser() {
|
|
|
2710
3273
|
}
|
|
2711
3274
|
renderer.requestRender();
|
|
2712
3275
|
}
|
|
2713
|
-
const statusBar = new
|
|
3276
|
+
const statusBar = new TextRenderable3(renderer, {
|
|
2714
3277
|
content: " Ready. Press Enter to search.",
|
|
2715
3278
|
backgroundColor: colors.surface,
|
|
2716
3279
|
color: colors.muted,
|
|
@@ -2735,7 +3298,8 @@ async function launchBrowser() {
|
|
|
2735
3298
|
statusMgr.set("idle", msg);
|
|
2736
3299
|
}
|
|
2737
3300
|
function setToolbar() {
|
|
2738
|
-
|
|
3301
|
+
const sort = currentMode === "packages" ? currentPackageSort : currentSort;
|
|
3302
|
+
toolbarText.content = formatToolbar(sort, currentLimit, totalCount);
|
|
2739
3303
|
renderer.requestRender();
|
|
2740
3304
|
}
|
|
2741
3305
|
function updateDetail(repo) {
|
|
@@ -2845,33 +3409,38 @@ async function launchBrowser() {
|
|
|
2845
3409
|
}
|
|
2846
3410
|
const xAxis = ` \u2506${"\u2500".repeat(termW)}`;
|
|
2847
3411
|
const weekLabels = ` \u2506 ${"1w".padEnd(Math.floor(termW / 4))} ${"13w".padEnd(Math.floor(termW / 4))} ${"26w".padEnd(Math.floor(termW / 4))} ${"52w"}`;
|
|
2848
|
-
return [xAxis, weekLabels];
|
|
3412
|
+
return [...lines, xAxis, weekLabels];
|
|
2849
3413
|
}
|
|
2850
3414
|
async function loadChart(repo) {
|
|
2851
3415
|
detailText.content = " Loading chart...";
|
|
2852
3416
|
renderer.requestRender();
|
|
2853
3417
|
try {
|
|
3418
|
+
const headers = { "User-Agent": "ghfind/1.0" };
|
|
3419
|
+
if (githubToken) headers.Authorization = `Bearer ${githubToken}`;
|
|
2854
3420
|
const [chartRes, repoRes] = await Promise.all([
|
|
2855
3421
|
fetch(
|
|
2856
3422
|
`https://api.github.com/repos/${repo.owner}/${repo.name}/stats/participation`,
|
|
2857
|
-
{ headers
|
|
3423
|
+
{ headers }
|
|
2858
3424
|
),
|
|
2859
3425
|
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`, {
|
|
2860
|
-
headers
|
|
2861
|
-
}).then((r) => r.ok ? r.json() : null)
|
|
3426
|
+
headers
|
|
3427
|
+
}).then((r) => r.ok ? r.clone().json() : null)
|
|
2862
3428
|
]);
|
|
2863
3429
|
let chartSection = "";
|
|
2864
3430
|
if (chartRes.ok) {
|
|
2865
3431
|
const data = await chartRes.json();
|
|
2866
3432
|
if (data?.all && data.all.length >= 2) {
|
|
2867
3433
|
chartCommitData = data.all;
|
|
2868
|
-
const
|
|
3434
|
+
const termW = graphFullscreen ? (process.stdout.columns || 120) - 12 : Math.floor((process.stdout.columns || 120) / 2) - 12;
|
|
3435
|
+
const chartW = Math.max(20, Math.min(60, termW));
|
|
2869
3436
|
const chartH = 10;
|
|
2870
3437
|
const chartLines = buildChartString(chartCommitData, chartW, chartH);
|
|
2871
|
-
chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join(
|
|
2872
|
-
|
|
2873
|
-
);
|
|
3438
|
+
chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join("\n");
|
|
3439
|
+
} else if (data?.message) {
|
|
3440
|
+
chartSection = ["", `Chart unavailable: ${data.message}`].join("\n");
|
|
2874
3441
|
}
|
|
3442
|
+
} else {
|
|
3443
|
+
chartSection = ["", `Chart unavailable (API ${chartRes.status})`].join("\n");
|
|
2875
3444
|
}
|
|
2876
3445
|
const desc = repo.description ?? "";
|
|
2877
3446
|
const lang = repo.language ?? "\u2014";
|
|
@@ -2931,6 +3500,7 @@ async function launchBrowser() {
|
|
|
2931
3500
|
searchBox.visible = false;
|
|
2932
3501
|
toolbarText.visible = false;
|
|
2933
3502
|
trendingTabBox.visible = true;
|
|
3503
|
+
body.visible = true;
|
|
2934
3504
|
renderTrendingTabs();
|
|
2935
3505
|
resultsSelect.options = [
|
|
2936
3506
|
{ name: " Loading trending...", description: "", value: null }
|
|
@@ -2982,12 +3552,69 @@ async function launchBrowser() {
|
|
|
2982
3552
|
function showSearchMode() {
|
|
2983
3553
|
currentMode = "search";
|
|
2984
3554
|
trendingTabBox.visible = false;
|
|
3555
|
+
gapBox.visible = false;
|
|
2985
3556
|
searchBox.visible = true;
|
|
2986
3557
|
toolbarText.visible = true;
|
|
3558
|
+
body.visible = true;
|
|
2987
3559
|
searchInput.placeholder = "Search GitHub repos (e.g. rust cli, or language:Rust stars:>100)";
|
|
2988
3560
|
searchInput.focus();
|
|
2989
3561
|
renderer.requestRender();
|
|
2990
3562
|
}
|
|
3563
|
+
function showPackagesMode() {
|
|
3564
|
+
currentMode = "packages";
|
|
3565
|
+
trendingTabBox.visible = false;
|
|
3566
|
+
gapBox.visible = false;
|
|
3567
|
+
searchBox.visible = true;
|
|
3568
|
+
toolbarText.visible = true;
|
|
3569
|
+
body.visible = true;
|
|
3570
|
+
searchInput.placeholder = "Search npm packages (e.g. react, vue, typescript)";
|
|
3571
|
+
searchInput.focus();
|
|
3572
|
+
packages = [];
|
|
3573
|
+
resultsSelect.options = [
|
|
3574
|
+
{ name: "", description: "Type a query and press Enter to search", value: null }
|
|
3575
|
+
];
|
|
3576
|
+
detailText.content = "";
|
|
3577
|
+
renderer.requestRender();
|
|
3578
|
+
}
|
|
3579
|
+
async function doPackageSearch(queryText) {
|
|
3580
|
+
const q = queryText.trim();
|
|
3581
|
+
if (q === "" || isLoading) return;
|
|
3582
|
+
currentQueryInput = q;
|
|
3583
|
+
isLoading = true;
|
|
3584
|
+
resultsSelect.options = [
|
|
3585
|
+
{ name: " Searching packages...", description: "", value: null }
|
|
3586
|
+
];
|
|
3587
|
+
detailText.content = "";
|
|
3588
|
+
statusMgr.set("searching", `Searching npm for "${q}"`);
|
|
3589
|
+
renderer.requestRender();
|
|
3590
|
+
try {
|
|
3591
|
+
const search = createPackageSearch();
|
|
3592
|
+
const result = await search.searchPackage(q, currentLimit);
|
|
3593
|
+
totalCount = result.totalCount;
|
|
3594
|
+
packageResultsRaw = result.packages;
|
|
3595
|
+
packages = sortPackages(packageResultsRaw, currentPackageSort);
|
|
3596
|
+
setToolbar();
|
|
3597
|
+
if (packages.length > 0) {
|
|
3598
|
+
resultsSelect.options = packages.map((p) => ({
|
|
3599
|
+
name: `${p.name}@${p.version}`,
|
|
3600
|
+
description: p.description ?? "",
|
|
3601
|
+
value: p
|
|
3602
|
+
}));
|
|
3603
|
+
resultsSelect.setSelectedIndex(0);
|
|
3604
|
+
statusMgr.set("success", `${packages.length} packages found`);
|
|
3605
|
+
} else {
|
|
3606
|
+
throw new NoResultsError(q);
|
|
3607
|
+
}
|
|
3608
|
+
} catch (err) {
|
|
3609
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3610
|
+
resultsSelect.options = [
|
|
3611
|
+
{ name: " (error)", description: "", value: null }
|
|
3612
|
+
];
|
|
3613
|
+
statusMgr.set("error", msg.slice(0, 60));
|
|
3614
|
+
}
|
|
3615
|
+
isLoading = false;
|
|
3616
|
+
renderer.requestRender();
|
|
3617
|
+
}
|
|
2991
3618
|
async function doSearch(queryText, append = false) {
|
|
2992
3619
|
const q = queryText.trim();
|
|
2993
3620
|
if (q === "" || isLoading) return;
|
|
@@ -3074,6 +3701,22 @@ async function launchBrowser() {
|
|
|
3074
3701
|
renderer.requestRender();
|
|
3075
3702
|
}
|
|
3076
3703
|
}
|
|
3704
|
+
function changePackageSort() {
|
|
3705
|
+
const idx = PACKAGE_SORT_MODES.findIndex((m) => m.key === currentPackageSort);
|
|
3706
|
+
const next = PACKAGE_SORT_MODES[(idx + 1) % PACKAGE_SORT_MODES.length];
|
|
3707
|
+
currentPackageSort = next.key;
|
|
3708
|
+
setToolbar();
|
|
3709
|
+
if (packageResultsRaw.length > 0) {
|
|
3710
|
+
packages = sortPackages(packageResultsRaw, currentPackageSort);
|
|
3711
|
+
resultsSelect.options = packages.map((p) => ({
|
|
3712
|
+
name: `${p.name}@${p.version}`,
|
|
3713
|
+
description: p.description ?? "",
|
|
3714
|
+
value: p
|
|
3715
|
+
}));
|
|
3716
|
+
resultsSelect.setSelectedIndex(0);
|
|
3717
|
+
renderer.requestRender();
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3077
3720
|
function changeLimit() {
|
|
3078
3721
|
const limits = [10, 25, 50, 100];
|
|
3079
3722
|
const idx = limits.indexOf(currentLimit);
|
|
@@ -3084,22 +3727,40 @@ async function launchBrowser() {
|
|
|
3084
3727
|
}
|
|
3085
3728
|
searchInput.on("enter", () => {
|
|
3086
3729
|
if (searchInput.value.trim() === "" || isLoading) return;
|
|
3087
|
-
|
|
3088
|
-
|
|
3730
|
+
if (currentMode === "packages") {
|
|
3731
|
+
doPackageSearch(searchInput.value);
|
|
3732
|
+
} else {
|
|
3733
|
+
doSearch(searchInput.value);
|
|
3734
|
+
}
|
|
3089
3735
|
});
|
|
3090
3736
|
resultsSelect.on("selectionChanged", () => {
|
|
3091
3737
|
const opt = resultsSelect.getSelectedOption();
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
if (
|
|
3095
|
-
|
|
3738
|
+
if (currentMode === "packages") {
|
|
3739
|
+
const pack = opt?.value;
|
|
3740
|
+
if (pack) {
|
|
3741
|
+
detailText.content = `Package: ${pack.name}@${pack.version}
|
|
3742
|
+
Downloads: ${pack.downloads.toLocaleString()}
|
|
3743
|
+
Score: ${pack.score.toFixed(2)}
|
|
3744
|
+
${pack.description ?? ""}`;
|
|
3745
|
+
}
|
|
3746
|
+
} else {
|
|
3747
|
+
const repo = opt?.value;
|
|
3748
|
+
if (repo) {
|
|
3749
|
+
if (graphFullscreen) loadChart(repo);
|
|
3750
|
+
else updateDetail(repo);
|
|
3751
|
+
}
|
|
3096
3752
|
}
|
|
3097
3753
|
renderer.requestRender();
|
|
3098
3754
|
});
|
|
3099
3755
|
resultsSelect.on("itemSelected", () => {
|
|
3100
3756
|
const opt = resultsSelect.getSelectedOption();
|
|
3101
|
-
|
|
3102
|
-
|
|
3757
|
+
if (currentMode === "packages") {
|
|
3758
|
+
const pack = opt?.value;
|
|
3759
|
+
if (pack?.url) openUrl(pack.url);
|
|
3760
|
+
} else {
|
|
3761
|
+
const repo = opt?.value;
|
|
3762
|
+
if (repo) openUrl(repo.url);
|
|
3763
|
+
}
|
|
3103
3764
|
});
|
|
3104
3765
|
renderer.keyInput.on("keypress", (key) => {
|
|
3105
3766
|
if (currentOverlay !== "none") {
|
|
@@ -3389,6 +4050,28 @@ async function launchBrowser() {
|
|
|
3389
4050
|
showLeaderMenu();
|
|
3390
4051
|
}
|
|
3391
4052
|
return;
|
|
4053
|
+
if (key.name === "t" && !searchInput.focused) {
|
|
4054
|
+
const current = config.theme;
|
|
4055
|
+
const themes = listThemes();
|
|
4056
|
+
const idx = themes.indexOf(current);
|
|
4057
|
+
const next = themes[(idx + 1) % themes.length];
|
|
4058
|
+
const nextTheme = loadTheme(next);
|
|
4059
|
+
Object.assign(colors, nextTheme);
|
|
4060
|
+
config.theme = next;
|
|
4061
|
+
saveConfig(config);
|
|
4062
|
+
renderer.requestRender();
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
if (key.name === "c" && !searchInput.focused) {
|
|
4067
|
+
if (currentOverlay === "compare") {
|
|
4068
|
+
showOverlay("none");
|
|
4069
|
+
} else {
|
|
4070
|
+
toggleCompareOnSelected();
|
|
4071
|
+
refreshCompare();
|
|
4072
|
+
showOverlay("compare");
|
|
4073
|
+
}
|
|
4074
|
+
return;
|
|
3392
4075
|
}
|
|
3393
4076
|
if (key.name === "?" || key.name === "h" && key.ctrl) {
|
|
3394
4077
|
showOverlay("help");
|
|
@@ -3471,18 +4154,23 @@ async function launchBrowser() {
|
|
|
3471
4154
|
currentVersion = pkg.version;
|
|
3472
4155
|
} catch {
|
|
3473
4156
|
}
|
|
4157
|
+
if (!session || !session.mode) {
|
|
4158
|
+
showLanding(true);
|
|
4159
|
+
}
|
|
3474
4160
|
checkForUpdateAndShow();
|
|
3475
4161
|
renderer.start();
|
|
3476
|
-
if (
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
searchInput.value = currentQueryInput;
|
|
3480
|
-
if (currentQueryInput) {
|
|
3481
|
-
doSearch(currentQueryInput);
|
|
4162
|
+
if (session && session.mode) {
|
|
4163
|
+
if (currentMode === "trending") {
|
|
4164
|
+
loadTrending();
|
|
3482
4165
|
} else {
|
|
3483
|
-
|
|
4166
|
+
searchInput.value = currentQueryInput;
|
|
4167
|
+
if (currentQueryInput) {
|
|
4168
|
+
doSearch(currentQueryInput);
|
|
4169
|
+
} else {
|
|
4170
|
+
showSearchMode();
|
|
4171
|
+
}
|
|
4172
|
+
searchInput.focus();
|
|
3484
4173
|
}
|
|
3485
|
-
searchInput.focus();
|
|
3486
4174
|
}
|
|
3487
4175
|
renderer.requestRender();
|
|
3488
4176
|
}
|
|
@@ -3513,6 +4201,7 @@ if (import.meta.main && false) {
|
|
|
3513
4201
|
}
|
|
3514
4202
|
|
|
3515
4203
|
// src/cli.ts
|
|
4204
|
+
process.env.GHFIND_CLI_RUN = "1";
|
|
3516
4205
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
3517
4206
|
var PKG_DIR2 = join3(__dirname2, "..");
|
|
3518
4207
|
function parseArgs(args) {
|
|
@@ -3531,7 +4220,10 @@ function parseArgs(args) {
|
|
|
3531
4220
|
interval: 300,
|
|
3532
4221
|
init: false,
|
|
3533
4222
|
version: false,
|
|
3534
|
-
help: false
|
|
4223
|
+
help: false,
|
|
4224
|
+
pkg: false,
|
|
4225
|
+
registry: "npm",
|
|
4226
|
+
compare: []
|
|
3535
4227
|
};
|
|
3536
4228
|
let queryParts = [];
|
|
3537
4229
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -3591,16 +4283,31 @@ function parseArgs(args) {
|
|
|
3591
4283
|
case "--format":
|
|
3592
4284
|
flags.format = args[++i];
|
|
3593
4285
|
break;
|
|
3594
|
-
case "--
|
|
3595
|
-
flags.
|
|
4286
|
+
case "--registry":
|
|
4287
|
+
flags.registry = args[++i] ?? "npm";
|
|
4288
|
+
break;
|
|
4289
|
+
case "--theme":
|
|
4290
|
+
flags.theme = args[++i] ?? "tokyo-night";
|
|
3596
4291
|
break;
|
|
3597
4292
|
case "--completion":
|
|
3598
4293
|
flags.completion = args[++i];
|
|
3599
4294
|
break;
|
|
4295
|
+
case "--compare": {
|
|
4296
|
+
const repos = [];
|
|
4297
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
4298
|
+
repos.push(args[++i]);
|
|
4299
|
+
}
|
|
4300
|
+
flags.compare = repos;
|
|
4301
|
+
break;
|
|
4302
|
+
}
|
|
3600
4303
|
default:
|
|
3601
4304
|
if (!arg.startsWith("-")) queryParts.push(arg);
|
|
3602
4305
|
}
|
|
3603
4306
|
}
|
|
4307
|
+
if (queryParts[0] === "pkg") {
|
|
4308
|
+
flags.pkg = true;
|
|
4309
|
+
queryParts.shift();
|
|
4310
|
+
}
|
|
3604
4311
|
flags.query = queryParts.join(" ");
|
|
3605
4312
|
return flags;
|
|
3606
4313
|
}
|
|
@@ -3624,6 +4331,9 @@ Usage:
|
|
|
3624
4331
|
ghfind <query> --format <fmt> Format lines (urls|names|ssh-urls|clone-commands|ids)
|
|
3625
4332
|
ghfind <query> --pipe <target> Pipe to clone/open
|
|
3626
4333
|
ghfind --trending --json Trending repos as JSON
|
|
4334
|
+
ghfind --compare <repo1> <repo2> Compare two+ repos side-by-side
|
|
4335
|
+
ghfind pkg <query> --json Search npm packages, output JSON
|
|
4336
|
+
ghfind pkg <query> Search npm packages, text list
|
|
3627
4337
|
ghfind --watch <query> Watch mode (poll every Ns)
|
|
3628
4338
|
ghfind init Run setup wizard
|
|
3629
4339
|
ghfind --completion <shell> Print completion script (bash|zsh|fish)
|
|
@@ -3634,11 +4344,13 @@ Options:
|
|
|
3634
4344
|
--json, --csv, --markdown, --count Output format
|
|
3635
4345
|
--limit <n> Max results (default: 50)
|
|
3636
4346
|
--sort <strategy> Sort: best-match|stars|updated|forks
|
|
3637
|
-
--
|
|
4347
|
+
--theme <name> Theme: tokyo-night|premium-dark (default: tokyo-night)
|
|
3638
4348
|
--trending Trending mode
|
|
3639
4349
|
--since <period> Trending period: daily|weekly|monthly
|
|
3640
4350
|
--pipe <target> Pipe target: clone|open
|
|
3641
4351
|
--format <fmt> Line format: urls|names|ssh-urls|...
|
|
4352
|
+
--registry <name> Registry for pkg: npm (default) | others TBD
|
|
4353
|
+
|
|
3642
4354
|
--watch Watch mode (re-run periodically)
|
|
3643
4355
|
--interval <s> Watch interval in seconds (default: 300)
|
|
3644
4356
|
--completion <shell> Generate completions
|
|
@@ -3660,13 +4372,22 @@ Options:
|
|
|
3660
4372
|
await runInitWizard();
|
|
3661
4373
|
return;
|
|
3662
4374
|
}
|
|
4375
|
+
if (flags.pkg) {
|
|
4376
|
+
const interactive = !flags.query && !flags.json && !flags.csv && !flags.markdown && !flags.count && !flags.format;
|
|
4377
|
+
if (interactive) {
|
|
4378
|
+
await launchPackageBrowser();
|
|
4379
|
+
} else {
|
|
4380
|
+
await runPackageSearch(flags);
|
|
4381
|
+
}
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
3663
4384
|
const outputFormat = flags.json ? "json" : flags.csv ? "csv" : flags.markdown ? "markdown" : void 0;
|
|
3664
4385
|
const isNonInteractive = outputFormat || flags.count || flags.format || flags.pipe || flags.watch;
|
|
3665
4386
|
if (isNonInteractive) {
|
|
3666
4387
|
await runNonInteractive(flags, outputFormat);
|
|
3667
4388
|
return;
|
|
3668
4389
|
}
|
|
3669
|
-
await launchBrowser();
|
|
4390
|
+
await launchBrowser(flags.theme);
|
|
3670
4391
|
}
|
|
3671
4392
|
function buildSearchContext(flags) {
|
|
3672
4393
|
const parsed = applyFlagFilters(parseQuery(flags.query), {});
|
|
@@ -3676,6 +4397,48 @@ function buildSearchContext(flags) {
|
|
|
3676
4397
|
provider: createGitHubSearch(void 0, flags.token ? [flags.token] : [])
|
|
3677
4398
|
};
|
|
3678
4399
|
}
|
|
4400
|
+
async function runPackageSearch(flags) {
|
|
4401
|
+
if (flags.registry !== "npm") {
|
|
4402
|
+
console.error(`Registry "${flags.registry}" not yet supported (only npm).`);
|
|
4403
|
+
process.exit(1);
|
|
4404
|
+
}
|
|
4405
|
+
const PKG_SORTS = ["best-match", "score", "downloads", "name"];
|
|
4406
|
+
const pkgSort = flags.sort;
|
|
4407
|
+
if (!PKG_SORTS.includes(pkgSort)) {
|
|
4408
|
+
console.error(`Invalid sort for pkg: "${flags.sort}" (use best-match|score|downloads|name).`);
|
|
4409
|
+
process.exit(1);
|
|
4410
|
+
}
|
|
4411
|
+
const search = createPackageSearch();
|
|
4412
|
+
const query = flags.query.trim();
|
|
4413
|
+
if (!query) {
|
|
4414
|
+
console.error("Usage: ghfind pkg <query> [--json|--csv|--markdown|--count|--format urls|names] [--limit <n>]");
|
|
4415
|
+
process.exit(1);
|
|
4416
|
+
}
|
|
4417
|
+
try {
|
|
4418
|
+
const { totalCount, packages } = await search.searchPackage(query, flags.limit);
|
|
4419
|
+
const sorted = sortPackages(packages, pkgSort);
|
|
4420
|
+
if (flags.count) {
|
|
4421
|
+
console.log(totalCount);
|
|
4422
|
+
} else if (flags.json) {
|
|
4423
|
+
console.log(formatPackagesJson(sorted));
|
|
4424
|
+
} else if (flags.csv) {
|
|
4425
|
+
console.log(formatPackagesCsv(sorted));
|
|
4426
|
+
} else if (flags.markdown) {
|
|
4427
|
+
console.log(formatPackagesMarkdown(sorted));
|
|
4428
|
+
} else if (flags.format === "urls") {
|
|
4429
|
+
console.log(formatPackagesUrls(sorted));
|
|
4430
|
+
} else if (flags.format === "names") {
|
|
4431
|
+
console.log(formatPackagesNames(sorted));
|
|
4432
|
+
} else {
|
|
4433
|
+
for (const p of sorted) {
|
|
4434
|
+
console.log(`${p.name}@${p.version} \u2193 ${p.downloads.toLocaleString()} ${p.score.toFixed(2)} ${p.description ?? ""}`);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
} catch (err) {
|
|
4438
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
4439
|
+
process.exit(1);
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
3679
4442
|
async function runNonInteractive(flags, outputFormat) {
|
|
3680
4443
|
const handlers = {
|
|
3681
4444
|
trending: async (ctx2) => {
|
|
@@ -3749,13 +4512,26 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
3749
4512
|
if (outputFormat) {
|
|
3750
4513
|
console.log(format(repos, outputFormat));
|
|
3751
4514
|
}
|
|
4515
|
+
},
|
|
4516
|
+
compare: async (_ctx) => {
|
|
4517
|
+
const search = createGitHubSearch(void 0, [flags.token ?? ""]);
|
|
4518
|
+
const repos = [];
|
|
4519
|
+
for (const fullName of flags.compare) {
|
|
4520
|
+
const res = await search.search(
|
|
4521
|
+
{ keywords: [fullName], qualifiers: [], raw: fullName },
|
|
4522
|
+
{ limit: 1, sort: "stars", json: false, verbose: false, token: flags.token }
|
|
4523
|
+
);
|
|
4524
|
+
repos.push(...res.repos);
|
|
4525
|
+
}
|
|
4526
|
+
console.log(buildComparisonTable(repos));
|
|
3752
4527
|
}
|
|
3753
4528
|
};
|
|
3754
|
-
const key = flags.trending && !flags.watch ? "trending" : flags.watch ? "watch" : flags.pipe ? "pipe" : flags.format ? "format" : "search";
|
|
4529
|
+
const key = flags.compare.length > 0 ? "compare" : flags.trending && !flags.watch ? "trending" : flags.watch ? "watch" : flags.pipe ? "pipe" : flags.format ? "format" : "search";
|
|
3755
4530
|
const ctx = buildSearchContext(flags);
|
|
3756
4531
|
await handlers[key](ctx);
|
|
3757
4532
|
}
|
|
3758
4533
|
main().catch((err) => {
|
|
4534
|
+
console.error("DEBUG: uncaught error:", err.message || err);
|
|
3759
4535
|
console.error(err instanceof Error ? err.message : String(err));
|
|
3760
4536
|
process.exit(1);
|
|
3761
4537
|
});
|