github-search-cli 9.1.0 → 9.1.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/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-TUMYR2IC.js";
5
+ } from "./chunk-IVUU2KGR.js";
7
6
 
8
7
  // src/errors.ts
9
8
  var SearchCliError = class extends Error {
@@ -513,8 +512,101 @@ function createTrendingSearch(logger2 = noopLogger) {
513
512
  return new SearchModule(new TrendingAdapter(logger2), logger2);
514
513
  }
515
514
 
516
- // src/output.ts
517
- import { writeFileSync } from "fs";
515
+ // src/package.ts
516
+ function normalizeNpmObject(obj) {
517
+ const p = obj.package;
518
+ const name = p.name ?? "";
519
+ return {
520
+ name,
521
+ version: p.version ?? "",
522
+ description: p.description ?? null,
523
+ url: p.links?.npm ?? `https://www.npmjs.com/package/${name}`,
524
+ downloads: obj.downloads?.monthly ?? 0,
525
+ score: obj.score?.final ?? obj.searchScore ?? 0,
526
+ publisher: p.publisher?.username ?? p.author?.name ?? null,
527
+ keywords: p.keywords ?? [],
528
+ registry: "npm"
529
+ };
530
+ }
531
+ function createPackageSearch() {
532
+ return {
533
+ async searchPackage(rawQuery, limit) {
534
+ const size = Math.min(Math.max(Math.trunc(limit) || 50, 1), 250);
535
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(rawQuery)}&size=${size}`;
536
+ const res = await fetch(url, {
537
+ headers: { "User-Agent": "ghfind", Accept: "application/json" }
538
+ });
539
+ if (!res.ok) {
540
+ throw new Error(`npm search failed: HTTP ${res.status}`);
541
+ }
542
+ const data = await res.json();
543
+ const packages = (data.objects ?? []).map(normalizeNpmObject);
544
+ return { totalCount: data.total ?? packages.length, packages };
545
+ }
546
+ };
547
+ }
548
+ var PACKAGE_SORT_MODES = [
549
+ { key: "best-match", label: "best-match" },
550
+ { key: "downloads", label: "downloads" },
551
+ { key: "score", label: "score" },
552
+ { key: "name", label: "name" }
553
+ ];
554
+ function sortPackages(pkgs, sort) {
555
+ if (sort === "best-match" || pkgs.length < 2) return pkgs;
556
+ const arr = [...pkgs];
557
+ if (sort === "score") {
558
+ arr.sort((a, b) => b.score - a.score);
559
+ } else if (sort === "downloads") {
560
+ arr.sort((a, b) => b.downloads - a.downloads);
561
+ } else {
562
+ arr.sort((a, b) => a.name.localeCompare(b.name));
563
+ }
564
+ return arr;
565
+ }
566
+ function csvEscape(val) {
567
+ const str = String(val ?? "");
568
+ if (str.includes('"') || str.includes(",") || str.includes("\n")) {
569
+ return `"${str.replace(/"/g, '""')}"`;
570
+ }
571
+ return str;
572
+ }
573
+ function formatPackagesJson(pkgs) {
574
+ return JSON.stringify(pkgs, null, 2);
575
+ }
576
+ function formatPackagesCsv(pkgs) {
577
+ const header = "rank,name,version,downloads,score,url";
578
+ const rows = pkgs.map(
579
+ (p, i) => `${i + 1},${csvEscape(p.name)},${p.version},${p.downloads},${p.score.toFixed(2)},${csvEscape(p.url)}`
580
+ );
581
+ return [header, ...rows].join("\n");
582
+ }
583
+ function formatPackagesMarkdown(pkgs) {
584
+ const header = "| # | Package | Version | Downloads | Score |";
585
+ const sep = "|---|---------|---------|-----------|-------|";
586
+ const rows = pkgs.map(
587
+ (p, i) => `| ${i + 1} | [${p.name}@${p.version}](${p.url}) | ${p.version} | ${p.downloads.toLocaleString()} | ${p.score.toFixed(2)} |`
588
+ );
589
+ return [header, sep, ...rows].join("\n");
590
+ }
591
+ function formatPackagesUrls(pkgs) {
592
+ return pkgs.map((p) => p.url).join("\n");
593
+ }
594
+ function formatPackagesNames(pkgs) {
595
+ return pkgs.map((p) => p.name).join("\n");
596
+ }
597
+
598
+ // src/package-browser.ts
599
+ import {
600
+ createCliRenderer,
601
+ BoxRenderable,
602
+ TextRenderable,
603
+ ScrollBoxRenderable,
604
+ StyledText,
605
+ bold,
606
+ fg,
607
+ dim,
608
+ t
609
+ } from "@opentui/core";
518
610
 
519
611
  // src/open-url.ts
520
612
  function openUrl(url) {
@@ -527,7 +619,282 @@ function openUrl(url) {
527
619
  }
528
620
  }
529
621
 
622
+ // src/package-browser.ts
623
+ var C = {
624
+ bg: "#1a1b26",
625
+ surface: "#16161e",
626
+ text: "#a9b1d6",
627
+ muted: "#565f89",
628
+ cyan: "#7dcfff",
629
+ purple: "#bb9af7",
630
+ green: "#9ece6a",
631
+ gold: "#e0af68",
632
+ orange: "#ff9e64",
633
+ red: "#f7768e",
634
+ blue: "#7aa2f7",
635
+ lightBlue: "#b4f9f8",
636
+ selectionBg: "#3b6ce8",
637
+ selectionText: "#c0caf5",
638
+ border: "#363b54",
639
+ descText: "#6c7086",
640
+ // lighter muted for descriptions
641
+ nameText: "#c0caf5"
642
+ // bright for package names
643
+ };
644
+ var INT_FMT = new Intl.NumberFormat("en-US");
645
+ function fmtCount(n) {
646
+ return INT_FMT.format(Math.trunc(n));
647
+ }
648
+ function formatPackageLine(pack) {
649
+ const nameStr = `${pack.name}@${pack.version}`.padEnd(28).slice(0, 28);
650
+ const dlStr = `\u2193 ${fmtCount(pack.downloads)}`.padEnd(20).slice(0, 20);
651
+ const scoreStr = `score ${pack.score.toFixed(2)}`.padEnd(12).slice(0, 12);
652
+ const desc = (pack.description ?? "").length > 60 ? (pack.description ?? "").slice(0, 57) + "..." : pack.description ?? "";
653
+ const line1 = t`${bold(fg(C.nameText)(nameStr))}${fg(C.cyan)(dlStr)}${fg(C.gold)(scoreStr)}`;
654
+ const line2 = t`${dim(fg(C.descText)(` ${desc}`))}`;
655
+ const newline = { text: "\n", __isChunk: true };
656
+ const chunks = [...line1.chunks, newline, ...line2.chunks];
657
+ return new StyledText(chunks);
658
+ }
659
+ async function launchPackageBrowser() {
660
+ let renderer;
661
+ try {
662
+ renderer = await createCliRenderer();
663
+ } catch (err) {
664
+ console.error(
665
+ "Failed to start the interactive browser.\n",
666
+ `Reason: ${err instanceof Error ? err.message : String(err)}
667
+ `
668
+ );
669
+ process.exit(1);
670
+ return;
671
+ }
672
+ const root = renderer.root;
673
+ root.flexDirection = "column";
674
+ let query = "";
675
+ let dirty = false;
676
+ let packages = [];
677
+ let selectedIdx = 0;
678
+ let isLoading = false;
679
+ const outerBox = new BoxRenderable(renderer, {
680
+ flexGrow: 1,
681
+ flexDirection: "column",
682
+ backgroundColor: C.bg,
683
+ border: true,
684
+ borderColor: C.border,
685
+ borderStyle: "rounded",
686
+ paddingX: 0,
687
+ paddingY: 0
688
+ });
689
+ const headerBox = new BoxRenderable(renderer, {
690
+ height: 1,
691
+ backgroundColor: C.bg,
692
+ paddingX: 1
693
+ });
694
+ const headerText = new TextRenderable(renderer, {
695
+ content: t`${bold(fg(C.purple)("\u27E0 tulipsearch"))}${fg(C.muted)(" \u2014 Packages")}`,
696
+ fg: C.purple,
697
+ bg: C.bg,
698
+ height: 1
699
+ });
700
+ headerBox.add(headerText);
701
+ outerBox.add(headerBox);
702
+ const inputBox = new BoxRenderable(renderer, {
703
+ height: 1,
704
+ flexDirection: "row",
705
+ backgroundColor: C.surface,
706
+ paddingX: 1
707
+ });
708
+ let inputText = null;
709
+ function renderInput() {
710
+ if (inputText) inputBox.remove(inputText);
711
+ const cursor = isLoading ? " " : "\u258A";
712
+ inputText = new TextRenderable(renderer, {
713
+ content: t`${fg(C.cyan)("\u276F ")}${query}${dim(fg(C.muted)(cursor))}`,
714
+ fg: C.text,
715
+ bg: C.surface,
716
+ height: 1
717
+ });
718
+ inputBox.add(inputText);
719
+ }
720
+ const statusBox = new BoxRenderable(renderer, {
721
+ height: 1,
722
+ backgroundColor: C.bg,
723
+ paddingX: 1
724
+ });
725
+ let statusText = null;
726
+ function setStatus(cfg) {
727
+ if (statusText) statusBox.remove(statusText);
728
+ statusText = new TextRenderable(renderer, {
729
+ content: cfg.content,
730
+ fg: cfg.color ?? C.text,
731
+ bg: C.bg,
732
+ height: 1
733
+ });
734
+ statusBox.add(statusText);
735
+ }
736
+ const scrollBox = new ScrollBoxRenderable(renderer, {
737
+ flexGrow: 1,
738
+ backgroundColor: C.bg,
739
+ scrollY: true,
740
+ scrollX: false,
741
+ viewportCulling: true
742
+ });
743
+ const rowBoxes = [];
744
+ const rowTexts = [];
745
+ function clearRows() {
746
+ for (const rb of rowBoxes) scrollBox.remove(rb);
747
+ rowBoxes.length = 0;
748
+ rowTexts.length = 0;
749
+ }
750
+ function rebuildList() {
751
+ clearRows();
752
+ packages.forEach((p, i) => {
753
+ const isEven = i % 2 === 0;
754
+ const isSelected = i === selectedIdx;
755
+ const rowBg = isSelected ? C.selectionBg : isEven ? C.bg : C.surface;
756
+ const rowText = new TextRenderable(renderer, {
757
+ content: formatPackageLine(p),
758
+ bg: rowBg,
759
+ height: 2
760
+ });
761
+ const rowBox = new BoxRenderable(renderer, {
762
+ flexDirection: "row",
763
+ backgroundColor: rowBg
764
+ });
765
+ rowBox.add(rowText);
766
+ rowBoxes.push(rowBox);
767
+ rowTexts.push(rowText);
768
+ scrollBox.add(rowBox);
769
+ });
770
+ }
771
+ const footerBox = new BoxRenderable(renderer, {
772
+ height: 1,
773
+ backgroundColor: C.bg,
774
+ paddingX: 1
775
+ });
776
+ const footerText = new TextRenderable(renderer, {
777
+ content: " type query \u21B5 search \u2191\u2193 navigate \u21B5 open q/esc quit",
778
+ fg: C.muted,
779
+ bg: C.bg,
780
+ height: 1
781
+ });
782
+ footerBox.add(footerText);
783
+ outerBox.add(footerBox);
784
+ root.add(outerBox);
785
+ outerBox.add(inputBox);
786
+ outerBox.add(statusBox);
787
+ const divider = new TextRenderable(renderer, {
788
+ content: "\u2500".repeat(80),
789
+ fg: C.border,
790
+ bg: C.bg,
791
+ height: 1
792
+ });
793
+ outerBox.add(divider);
794
+ outerBox.add(scrollBox);
795
+ async function runSearch() {
796
+ const q = query.trim();
797
+ if (!q) return;
798
+ isLoading = true;
799
+ dirty = false;
800
+ clearRows();
801
+ setStatus({ content: t`${dim(fg(C.muted)(` Loading packages for "${q}"...`))}` });
802
+ renderer.requestRender();
803
+ try {
804
+ const searcher = createPackageSearch();
805
+ const res = await searcher.searchPackage(q, 25);
806
+ packages = res.packages;
807
+ selectedIdx = 0;
808
+ rebuildList();
809
+ setStatus({
810
+ content: t`${fg(C.muted)(` ${packages.length} of ${fmtCount(res.totalCount)} results for `)}${fg(C.cyan)(`"${q}"`)}`
811
+ });
812
+ } catch (err) {
813
+ packages = [];
814
+ clearRows();
815
+ const msg = err instanceof Error ? err.message : String(err);
816
+ setStatus({
817
+ content: t`${fg(C.red)(` Error: ${msg.slice(0, 70)} \u2014 press \u21B5 to retry`)}`
818
+ });
819
+ }
820
+ isLoading = false;
821
+ renderInput();
822
+ renderer.requestRender();
823
+ }
824
+ function openSelectedPackage() {
825
+ if (selectedIdx < 0 || selectedIdx >= packages.length) return;
826
+ const p = packages[selectedIdx];
827
+ openUrl(p.url);
828
+ }
829
+ function moveSelection(delta) {
830
+ const oldIdx = selectedIdx;
831
+ const newIdx = Math.max(0, Math.min(packages.length - 1, oldIdx + delta));
832
+ if (newIdx === oldIdx) return;
833
+ selectedIdx = newIdx;
834
+ const setBg = (idx, selected) => {
835
+ if (idx < 0 || idx >= rowBoxes.length) return;
836
+ const bg = selected ? C.selectionBg : idx % 2 === 0 ? C.bg : C.surface;
837
+ rowBoxes[idx].backgroundColor = bg;
838
+ if (rowTexts[idx]) rowTexts[idx].bg = bg;
839
+ };
840
+ setBg(oldIdx, false);
841
+ setBg(newIdx, true);
842
+ renderer.requestRender();
843
+ }
844
+ renderer.keyInput.on("keypress", (key) => {
845
+ const name = key.name || "";
846
+ if (name === "q" || name === "escape") {
847
+ process.exit(0);
848
+ return;
849
+ }
850
+ if (name === "backspace") {
851
+ query = query.slice(0, -1);
852
+ dirty = true;
853
+ renderInput();
854
+ renderer.requestRender();
855
+ return;
856
+ }
857
+ if (name === "space") {
858
+ query += " ";
859
+ dirty = true;
860
+ renderInput();
861
+ renderer.requestRender();
862
+ return;
863
+ }
864
+ if (name.length === 1) {
865
+ query += name;
866
+ dirty = true;
867
+ renderInput();
868
+ renderer.requestRender();
869
+ return;
870
+ }
871
+ if (name === "up" || name === "k") {
872
+ moveSelection(-1);
873
+ return;
874
+ }
875
+ if (name === "down" || name === "j") {
876
+ moveSelection(1);
877
+ return;
878
+ }
879
+ if (name === "enter" || name === "return") {
880
+ if (dirty || packages.length === 0) {
881
+ runSearch();
882
+ } else {
883
+ openSelectedPackage();
884
+ }
885
+ return;
886
+ }
887
+ });
888
+ renderer.start();
889
+ renderInput();
890
+ setStatus({
891
+ content: t`${fg(C.muted)(" Type a package query above and press \u21B5 to search")}`
892
+ });
893
+ renderer.requestRender();
894
+ }
895
+
530
896
  // src/output.ts
897
+ import { writeFileSync } from "fs";
531
898
  var FORMATS = /* @__PURE__ */ new Map([
532
899
  // Export formats
533
900
  ["json", formatJson],
@@ -544,7 +911,7 @@ var FORMATS = /* @__PURE__ */ new Map([
544
911
  function formatJson(repos) {
545
912
  return JSON.stringify(repos, null, 2);
546
913
  }
547
- function csvEscape(val) {
914
+ function csvEscape2(val) {
548
915
  const str = String(val ?? "");
549
916
  if (str.includes('"') || str.includes(",") || str.includes("\n")) {
550
917
  return `"${str.replace(/"/g, '""')}"`;
@@ -554,7 +921,7 @@ function csvEscape(val) {
554
921
  function formatCsv(repos) {
555
922
  const header = "rank,full_name,stars,forks,language,url";
556
923
  const rows = repos.map(
557
- (r, i) => `${i + 1},${csvEscape(r.fullName)},${r.stars},${r.forks},${csvEscape(r.language ?? "")},${csvEscape(r.url)}`
924
+ (r, i) => `${i + 1},${csvEscape2(r.fullName)},${r.stars},${r.forks},${csvEscape2(r.language ?? "")},${csvEscape2(r.url)}`
558
925
  );
559
926
  return [header, ...rows].join("\n");
560
927
  }
@@ -664,36 +1031,42 @@ import { createInterface } from "readline";
664
1031
 
665
1032
  // src/themes.ts
666
1033
  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
1034
  "tokyo-night": {
691
1035
  name: "tokyo-night",
692
- bg: "#3D3B3B",
693
- surface: "#4a4848",
694
- surfaceAlt: "#525050",
695
- text: "#e0e4f0",
696
- muted: "#a8a6a6",
1036
+ // Genuine Tokyo Night palette — cool blue-violet mood
1037
+ bg: "#1a1b26",
1038
+ surface: "#24283b",
1039
+ surfaceAlt: "#292e42",
1040
+ text: "#c0caf5",
1041
+ muted: "#565f89",
1042
+ blue: "#7aa2f7",
1043
+ green: "#9ece6a",
1044
+ yellow: "#e0af68",
1045
+ red: "#f7768e",
1046
+ teal: "#73daca",
1047
+ purple: "#bb9af7",
1048
+ orange: "#ff9e64",
1049
+ border: "#24283b",
1050
+ borderAlt: "#1f2335",
1051
+ separator: "#1f2335",
1052
+ selectionBg: "#2d3f76",
1053
+ selectionText: "#c0caf5",
1054
+ success: "#9ece6a",
1055
+ warning: "#e0af68",
1056
+ error: "#f7768e",
1057
+ surfaceDim: "#16161e",
1058
+ accent: "#7aa2f7",
1059
+ accentDim: "#5d7abf",
1060
+ borderAccent: "#1f2335"
1061
+ },
1062
+ "premium-dark": {
1063
+ name: "premium-dark",
1064
+ // Warm counter-mood to Tokyo Night — amber/rust accent
1065
+ bg: "#1e1e2e",
1066
+ surface: "#262637",
1067
+ surfaceAlt: "#2e2e42",
1068
+ text: "#cdd6f4",
1069
+ muted: "#6c7086",
697
1070
  blue: "#89b4fa",
698
1071
  green: "#a6e3a1",
699
1072
  yellow: "#f9e2af",
@@ -701,60 +1074,18 @@ var THEMES = {
701
1074
  teal: "#94e2d5",
702
1075
  purple: "#cba6f7",
703
1076
  orange: "#fab387",
704
- border: "#1C1C1C",
705
- borderAlt: "#2a2a2a",
706
- separator: "#1C1C1C",
707
- selectionBg: "#2A2A9C",
708
- selectionText: "#ffffff",
1077
+ border: "#313244",
1078
+ borderAlt: "#2a2a3d",
1079
+ separator: "#2a2a3d",
1080
+ selectionBg: "#45475a",
1081
+ selectionText: "#cdd6f4",
709
1082
  success: "#a6e3a1",
710
1083
  warning: "#f9e2af",
711
- error: "#f38ba8"
712
- },
713
- dracula: {
714
- name: "dracula",
715
- bg: "#3D3B3B",
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"
1084
+ error: "#f38ba8",
1085
+ surfaceDim: "#181825",
1086
+ accent: "#f9e2af",
1087
+ accentDim: "#c9a87a",
1088
+ borderAccent: "#313244"
758
1089
  }
759
1090
  };
760
1091
  function loadTheme(name) {
@@ -790,13 +1121,13 @@ async function runInitWizard() {
790
1121
  const config = {
791
1122
  defaultSort: ["best-match", "stars", "updated", "forks"].includes(sortStr) ? sortStr : "best-match",
792
1123
  defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr) || 50)),
793
- theme: themes.includes(themeStr) ? themeStr : "tokyo-night",
1124
+ theme: themes.includes(themeStr) ? themeStr : themes[0],
794
1125
  cacheTtlSeconds: 300,
795
1126
  defaultTab: "search"
796
1127
  };
797
1128
  if (token) config.githubToken = token;
798
1129
  saveConfig(config);
799
- const { configPath } = await import("./config-DFL44OVL.js");
1130
+ const { configPath } = await import("./config-GNZOQRTH.js");
800
1131
  console.log(` \u2713 Config saved to ${configPath()}`);
801
1132
  console.log(" Run 'ghfind' to start browsing!");
802
1133
  console.log("");
@@ -809,25 +1140,25 @@ import { fileURLToPath as fileURLToPath2 } from "url";
809
1140
 
810
1141
  // src/tui.ts
811
1142
  import {
812
- createCliRenderer as createCliRenderer2,
813
- BoxRenderable as BoxRenderable2,
814
- TextRenderable as TextRenderable2,
1143
+ createCliRenderer as createCliRenderer3,
1144
+ BoxRenderable as BoxRenderable3,
1145
+ TextRenderable as TextRenderable3,
815
1146
  InputRenderable,
816
1147
  SelectRenderable,
817
- ScrollBoxRenderable as ScrollBoxRenderable2
1148
+ ScrollBoxRenderable as ScrollBoxRenderable3
818
1149
  } from "@opentui/core";
819
1150
 
820
1151
  // src/trending.ts
821
1152
  import {
822
- createCliRenderer,
823
- BoxRenderable,
824
- TextRenderable,
825
- ScrollBoxRenderable,
826
- StyledText,
827
- bold,
828
- fg,
829
- dim,
830
- t
1153
+ createCliRenderer as createCliRenderer2,
1154
+ BoxRenderable as BoxRenderable2,
1155
+ TextRenderable as TextRenderable2,
1156
+ ScrollBoxRenderable as ScrollBoxRenderable2,
1157
+ StyledText as StyledText2,
1158
+ bold as bold2,
1159
+ fg as fg2,
1160
+ dim as dim2,
1161
+ t as t2
831
1162
  } from "@opentui/core";
832
1163
  var TAB_NAMES = ["Today", "This Week", "This Month"];
833
1164
  function tabSince(tab) {
@@ -853,6 +1184,16 @@ if (import.meta.main && false) {
853
1184
  // src/help.ts
854
1185
  function buildHelpSections() {
855
1186
  return [
1187
+ {
1188
+ title: " Landing ",
1189
+ titleColor: "blue",
1190
+ rows: [
1191
+ { keys: "\u2191 \u2193 / j k", action: "Navigate menu cards" },
1192
+ { keys: "Enter", action: "Select mode" },
1193
+ { keys: "? / h", action: "Toggle this help screen" },
1194
+ { keys: "q", action: "Quit" }
1195
+ ]
1196
+ },
856
1197
  {
857
1198
  title: " Navigation ",
858
1199
  titleColor: "blue",
@@ -882,6 +1223,17 @@ function buildHelpSections() {
882
1223
  { keys: "\u2190 \u2192 / h l", action: "Navigate tabs" }
883
1224
  ]
884
1225
  },
1226
+ {
1227
+ title: " Packages Mode ",
1228
+ titleColor: "cyan",
1229
+ rows: [
1230
+ { keys: "/", action: "Search npm packages" },
1231
+ { keys: "\u2191 \u2193 / j k", action: "Move selection" },
1232
+ { keys: "Enter", action: "View package details" },
1233
+ { keys: "Space", action: "Open command menu" }
1234
+ ],
1235
+ note: "Search by name, description, or tags. Sort by downloads or score."
1236
+ },
885
1237
  {
886
1238
  title: " Command Menu (Space)",
887
1239
  titleColor: "purple",
@@ -894,9 +1246,17 @@ function buildHelpSections() {
894
1246
  { keys: "Deep-dive", action: "Languages + contributors + excerpt" },
895
1247
  { keys: "Bookmark", action: "Save / unsave selected repo" },
896
1248
  { keys: "Compare", action: "Add to / remove from comparison" },
897
- { keys: "Trending", action: "Switch to trending browser" }
1249
+ { keys: "Trending", action: "Switch to trending browser" },
1250
+ { keys: "Packages", action: "Switch to npm packages" },
1251
+ { keys: "Export", action: "Export results to file" },
1252
+ { keys: "Share", action: "Share repo as text" },
1253
+ { keys: "Topics", action: "Browse GitHub topics" },
1254
+ { keys: "Saved", action: "View saved searches" },
1255
+ { keys: "Notifs", action: "Check GitHub notifications" },
1256
+ { keys: "History", action: "View search history" },
1257
+ { keys: "Help", action: "Show this help screen" }
898
1258
  ],
899
- note: "Also: Open, History, Export, Share, Topics, Saved, Notifs, Help"
1259
+ note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc"
900
1260
  },
901
1261
  {
902
1262
  title: " Overlay Keys ",
@@ -905,7 +1265,8 @@ function buildHelpSections() {
905
1265
  { keys: "Esc / q", action: "Close any overlay" },
906
1266
  { keys: "Enter", action: "Confirm selection / run action" },
907
1267
  { keys: "d", action: "Delete entry (history, bookmarks, saved)" },
908
- { keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" }
1268
+ { keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" },
1269
+ { keys: "\u2190 \u2192 / h l", action: "Navigate tabs / panels" }
909
1270
  ]
910
1271
  },
911
1272
  {
@@ -915,23 +1276,26 @@ function buildHelpSections() {
915
1276
  { keys: "keywords", action: "Free-text search" },
916
1277
  { keys: "key:value", action: "Qualifier filter" },
917
1278
  { keys: "-key:value", action: "Negated qualifier" },
918
- { keys: '"phrase"', action: "Exact phrase match" }
1279
+ { keys: '"phrase"', action: "Exact phrase match" },
1280
+ { keys: "language:TypeScript", action: "Filter by language" },
1281
+ { keys: "stars:>1000", action: "Minimum stars" },
1282
+ { keys: "fork:true", action: "Include forks" },
1283
+ { keys: "user:octocat", action: "User's repos" },
1284
+ { keys: "org:facebook", action: "Organization repos" },
1285
+ { keys: "repo:facebook/react", action: "Specific repo" },
1286
+ { keys: "topic:react", action: "Filter by topic" },
1287
+ { keys: "license:mit", action: "License type" },
1288
+ { keys: "pushed:>2024-01-01", action: "Last push date" },
1289
+ { keys: "archived:false", action: "Exclude archived" }
919
1290
  ]
920
1291
  },
921
1292
  {
922
- title: " Qualifiers ",
1293
+ title: " Utility Keys ",
923
1294
  titleColor: "muted",
924
1295
  rows: [
925
- { keys: "language:", action: "Filter by language" },
926
- { keys: "stars:>N", action: "Minimum stars" },
927
- { keys: "fork:true", action: "Include forks" },
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" }
1296
+ { keys: "t", action: "Cycle theme (light / dark / solarized)" },
1297
+ { keys: "Ctrl+X", action: "Copy repo URL to clipboard" },
1298
+ { keys: "Ctrl+C", action: "Copy search query to clipboard" }
935
1299
  ]
936
1300
  },
937
1301
  {
@@ -1465,7 +1829,6 @@ var colors = {
1465
1829
  surfaceAlt: "#525050",
1466
1830
  text: "#e0e4f0",
1467
1831
  muted: "#a8a6a6",
1468
- blue: "#89b4fa",
1469
1832
  green: "#a6e3a1",
1470
1833
  yellow: "#f9e2af",
1471
1834
  red: "#f38ba8",
@@ -1500,10 +1863,20 @@ var SORT_MODES = [
1500
1863
  { key: "updated", label: "updated" },
1501
1864
  { key: "forks", label: "forks" }
1502
1865
  ];
1503
- async function launchBrowser() {
1866
+ async function launchBrowser(theme_override) {
1504
1867
  let renderer;
1505
1868
  try {
1506
- renderer = await createCliRenderer2();
1869
+ const isTTY = process.stdout.isTTY && process.stdin.isTTY;
1870
+ if (isTTY) {
1871
+ renderer = await createCliRenderer3();
1872
+ } else {
1873
+ renderer = await createCliRenderer3({
1874
+ width: 80,
1875
+ height: 24,
1876
+ screenMode: "main-screen",
1877
+ autoFocus: false
1878
+ });
1879
+ }
1507
1880
  } catch (err) {
1508
1881
  const isNode = process.versions.bun === void 0;
1509
1882
  if (isNode) {
@@ -1526,10 +1899,16 @@ async function launchBrowser() {
1526
1899
  }
1527
1900
  const root = renderer.root;
1528
1901
  root.flexDirection = "column";
1902
+ root.border = true;
1903
+ root.borderTop = true;
1904
+ root.borderBottom = true;
1905
+ root.borderLeft = true;
1906
+ root.borderRight = true;
1907
+ root.borderColor = colors.borderAccent;
1529
1908
  root.backgroundColor = colors.bg;
1530
1909
  const config = loadConfig();
1531
1910
  const githubToken = config.githubToken || process.env.GITHUB_TOKEN;
1532
- const theme = loadTheme(config.theme);
1911
+ const theme = loadTheme(theme_override || config.theme);
1533
1912
  Object.assign(colors, theme);
1534
1913
  const session = restoreSession();
1535
1914
  let currentRepos = [];
@@ -1538,6 +1917,9 @@ async function launchBrowser() {
1538
1917
  let currentQueryInput = session?.query ?? "";
1539
1918
  let isLoading = false;
1540
1919
  let currentMode = session?.mode ?? "search";
1920
+ let packages = [];
1921
+ let currentPackageSort = "best-match";
1922
+ let packageResultsRaw = [];
1541
1923
  let trendingTab = session?.trendingTab ?? "This Week";
1542
1924
  let trendingPeriod = "this week";
1543
1925
  let graphFullscreen = false;
@@ -1548,7 +1930,7 @@ async function launchBrowser() {
1548
1930
  let deepDiveActive = false;
1549
1931
  let compareList = [];
1550
1932
  let quitArmed = false;
1551
- const header = new TextRenderable2(renderer, {
1933
+ const header = new TextRenderable3(renderer, {
1552
1934
  content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
1553
1935
  backgroundColor: colors.bg,
1554
1936
  color: colors.muted,
@@ -1556,7 +1938,7 @@ async function launchBrowser() {
1556
1938
  borderBottom: true,
1557
1939
  borderBottomColor: colors.border
1558
1940
  });
1559
- const trendingTabBox = new BoxRenderable2(renderer, {
1941
+ const trendingTabBox = new BoxRenderable3(renderer, {
1560
1942
  flexDirection: "row",
1561
1943
  height: 1,
1562
1944
  backgroundColor: colors.surface,
@@ -1566,12 +1948,12 @@ async function launchBrowser() {
1566
1948
  });
1567
1949
  const trendingTabTexts = [];
1568
1950
  function renderTrendingTabs() {
1569
- for (const t2 of trendingTabTexts) trendingTabBox.remove(t2);
1951
+ for (const t3 of trendingTabTexts) trendingTabBox.remove(t3);
1570
1952
  trendingTabTexts.length = 0;
1571
1953
  TAB_NAMES.forEach((name, i) => {
1572
1954
  const isActive = name === trendingTab;
1573
1955
  const label = ` ${name} `;
1574
- const tt = new TextRenderable2(renderer, {
1956
+ const tt = new TextRenderable3(renderer, {
1575
1957
  content: label,
1576
1958
  color: isActive ? colors.bg : colors.muted,
1577
1959
  backgroundColor: isActive ? colors.blue : colors.bg,
@@ -1580,7 +1962,7 @@ async function launchBrowser() {
1580
1962
  trendingTabTexts.push(tt);
1581
1963
  trendingTabBox.add(tt);
1582
1964
  if (i < TAB_NAMES.length - 1) {
1583
- const sp = new TextRenderable2(renderer, {
1965
+ const sp = new TextRenderable3(renderer, {
1584
1966
  content: " ",
1585
1967
  color: colors.muted,
1586
1968
  backgroundColor: colors.bg,
@@ -1593,16 +1975,20 @@ async function launchBrowser() {
1593
1975
  }
1594
1976
  root.add(header);
1595
1977
  root.add(trendingTabBox);
1596
- const searchBox = new BoxRenderable2(renderer, {
1978
+ const gapBox = new BoxRenderable3(renderer, {
1979
+ height: 1,
1980
+ visible: true
1981
+ });
1982
+ root.add(gapBox);
1983
+ const searchBox = new BoxRenderable3(renderer, {
1597
1984
  visible: true,
1598
- borderBottom: true,
1599
- borderBottomColor: colors.border,
1600
- title: " SEARCH ",
1985
+ bordered: true,
1986
+ borderColor: colors.border,
1987
+ title: "",
1601
1988
  titleColor: colors.blue,
1602
1989
  width: "100%",
1603
1990
  flexDirection: "row",
1604
- paddingTop: 0,
1605
- paddingBottom: 0,
1991
+ marginTop: 1,
1606
1992
  backgroundColor: colors.surface
1607
1993
  });
1608
1994
  const searchInput = new InputRenderable(renderer, {
@@ -1617,7 +2003,7 @@ async function launchBrowser() {
1617
2003
  });
1618
2004
  searchBox.add(searchInput);
1619
2005
  root.add(searchBox);
1620
- const toolbarText = new TextRenderable2(renderer, {
2006
+ const toolbarText = new TextRenderable3(renderer, {
1621
2007
  content: formatToolbar(currentSort, currentLimit, totalCount),
1622
2008
  visible: true,
1623
2009
  // hidden in trending mode
@@ -1630,15 +2016,16 @@ async function launchBrowser() {
1630
2016
  paddingX: 1
1631
2017
  });
1632
2018
  root.add(toolbarText);
1633
- const body = new BoxRenderable2(renderer, {
2019
+ const body = new BoxRenderable3(renderer, {
1634
2020
  flexDirection: "row",
1635
2021
  flexGrow: 1,
1636
2022
  gap: 1,
1637
2023
  paddingY: 1,
1638
2024
  paddingX: 1
1639
2025
  });
1640
- const resultsBox = new BoxRenderable2(renderer, {
1641
- borderBottom: true,
2026
+ const resultsBox = new BoxRenderable3(renderer, {
2027
+ bordered: true,
2028
+ borderColor: colors.border,
1642
2029
  borderBottomColor: colors.border,
1643
2030
  title: " Results ",
1644
2031
  titleColor: colors.blue,
@@ -1666,8 +2053,9 @@ async function launchBrowser() {
1666
2053
  itemSpacing: 0
1667
2054
  });
1668
2055
  resultsBox.add(resultsSelect);
1669
- const detailBox = new BoxRenderable2(renderer, {
1670
- borderBottom: true,
2056
+ const detailBox = new BoxRenderable3(renderer, {
2057
+ bordered: true,
2058
+ borderColor: colors.border,
1671
2059
  borderBottomColor: colors.border,
1672
2060
  title: " Details ",
1673
2061
  titleColor: colors.green,
@@ -1678,16 +2066,174 @@ async function launchBrowser() {
1678
2066
  focusable: false,
1679
2067
  backgroundColor: colors.surface
1680
2068
  });
1681
- const detailText = new TextRenderable2(renderer, {
2069
+ const detailText = new TextRenderable3(renderer, {
1682
2070
  content: "",
1683
- color: colors.text
2071
+ color: colors.text,
2072
+ wrapMode: "none"
1684
2073
  });
1685
2074
  detailBox.add(detailText);
1686
2075
  body.add(resultsBox);
1687
2076
  body.add(detailBox);
1688
2077
  root.add(body);
2078
+ const landingBox = new BoxRenderable3(renderer, {
2079
+ visible: false,
2080
+ flexGrow: 1,
2081
+ flexDirection: "column",
2082
+ alignItems: "center",
2083
+ justifyContent: "center"
2084
+ });
2085
+ root.add(landingBox);
2086
+ const landingBanner = new TextRenderable3(renderer, {
2087
+ content: [
2088
+ " \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 ",
2089
+ "\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",
2090
+ "\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",
2091
+ "\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",
2092
+ "\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",
2093
+ " \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"
2094
+ ].join("\n"),
2095
+ color: colors.accent,
2096
+ backgroundColor: colors.bg,
2097
+ height: 6
2098
+ });
2099
+ landingBox.add(landingBanner);
2100
+ const landingOptions = [
2101
+ {
2102
+ icon: "\u{1F50D}",
2103
+ title: "Repo Search",
2104
+ desc: "Search GitHub repositories by query, language, stars, and more",
2105
+ action: () => {
2106
+ currentMode = "search";
2107
+ showLanding(false);
2108
+ showSearchMode();
2109
+ }
2110
+ },
2111
+ {
2112
+ icon: "\u{1F4E6}",
2113
+ title: "Package Search",
2114
+ desc: "Search npm packages by name, description, or tags",
2115
+ action: () => {
2116
+ currentMode = "packages";
2117
+ showLanding(false);
2118
+ showPackagesMode();
2119
+ }
2120
+ },
2121
+ {
2122
+ icon: "\u{1F525}",
2123
+ title: "Trending",
2124
+ desc: "Browse the hottest repos on GitHub right now",
2125
+ action: () => {
2126
+ currentMode = "trending";
2127
+ showLanding(false);
2128
+ loadTrending();
2129
+ }
2130
+ }
2131
+ ];
2132
+ let landingSelected = 0;
2133
+ const landingCards = [];
2134
+ for (let i = 0; i < landingOptions.length; i++) {
2135
+ const opt = landingOptions[i];
2136
+ const card = new BoxRenderable3(renderer, {
2137
+ flexDirection: "row",
2138
+ height: 1,
2139
+ width: "60%",
2140
+ marginTop: 1,
2141
+ backgroundColor: void 0,
2142
+ borderColor: i === 0 ? colors.accent : colors.border,
2143
+ borderWidth: 1,
2144
+ paddingX: 1,
2145
+ alignItems: "center",
2146
+ focusable: false
2147
+ });
2148
+ landingCards.push(card);
2149
+ landingBox.add(card);
2150
+ const titleLine = new TextRenderable3(renderer, {
2151
+ content: opt.title,
2152
+ color: i === 0 ? colors.accent : colors.text,
2153
+ backgroundColor: void 0,
2154
+ height: 1,
2155
+ flexGrow: 1
2156
+ });
2157
+ card.add(titleLine);
2158
+ }
2159
+ const landingHint = new TextRenderable3(renderer, {
2160
+ content: " \u2191\u2193 Navigate \u21E9 Select [?]help [q]uit",
2161
+ color: colors.muted,
2162
+ backgroundColor: colors.bg,
2163
+ height: 1,
2164
+ marginTop: 1
2165
+ });
2166
+ landingBox.add(landingHint);
2167
+ function showLanding(visible) {
2168
+ landingBox.visible = visible;
2169
+ if (visible) {
2170
+ currentMode = "landing";
2171
+ hideMainContent();
2172
+ landingSelected = 0;
2173
+ updateLandingCards();
2174
+ renderer.requestRender();
2175
+ }
2176
+ }
2177
+ function updateLandingCards() {
2178
+ for (let i = 0; i < landingCards.length; i++) {
2179
+ const opt = landingOptions[i];
2180
+ const isSel = i === landingSelected;
2181
+ landingCards[i].backgroundColor = isSel ? colors.selectionBg : colors.surface;
2182
+ landingCards[i].borderColor = isSel ? colors.accent : colors.border;
2183
+ landingCards[i].borderWidth = isSel ? 1 : 0;
2184
+ const children = landingCards[i].children ?? [];
2185
+ if (children[0]) {
2186
+ const iconCol = children[0];
2187
+ const iconChildren = iconCol.children ?? [];
2188
+ const iconTextChild = iconChildren[0];
2189
+ if (iconTextChild) {
2190
+ iconTextChild.color = isSel ? colors.accent : colors.text;
2191
+ iconTextChild.backgroundColor = isSel ? colors.selectionBg : colors.surface;
2192
+ }
2193
+ const iconSpacerChild = iconChildren[1];
2194
+ if (iconSpacerChild) {
2195
+ iconSpacerChild.backgroundColor = isSel ? colors.selectionBg : colors.surface;
2196
+ }
2197
+ }
2198
+ if (children[1]) {
2199
+ const contentCol = children[1];
2200
+ const contentChildren = contentCol.children ?? [];
2201
+ const titleChild = contentChildren[0];
2202
+ if (titleChild) {
2203
+ titleChild.color = isSel ? colors.accent : colors.text;
2204
+ titleChild.backgroundColor = isSel ? colors.selectionBg : colors.surface;
2205
+ }
2206
+ const descChild = contentChildren[1];
2207
+ if (descChild) {
2208
+ descChild.backgroundColor = isSel ? colors.selectionBg : colors.surface;
2209
+ }
2210
+ const hintChild = contentChildren[2];
2211
+ }
2212
+ }
2213
+ }
2214
+ renderer.keyInput.on("keypress", (key) => {
2215
+ if (currentMode === "landing") {
2216
+ if (key.name === "up" || key.name === "k") {
2217
+ landingSelected = Math.max(0, landingSelected - 1);
2218
+ updateLandingCards();
2219
+ renderer.requestRender();
2220
+ } else if (key.name === "down" || key.name === "j") {
2221
+ landingSelected = Math.min(landingOptions.length - 1, landingSelected + 1);
2222
+ updateLandingCards();
2223
+ renderer.requestRender();
2224
+ } else if (key.name === "enter" || key.name === "return") {
2225
+ landingOptions[landingSelected].action();
2226
+ } else if (key.name === "?" || key.name === "h") {
2227
+ showOverlay(currentOverlay === "help" ? "none" : "help");
2228
+ } else if (key.name === "q") {
2229
+ cleanup();
2230
+ }
2231
+ return;
2232
+ }
2233
+ });
1689
2234
  function hideMainContent() {
1690
2235
  trendingTabBox.visible = false;
2236
+ gapBox.visible = false;
1691
2237
  searchBox.visible = false;
1692
2238
  toolbarText.visible = false;
1693
2239
  body.visible = false;
@@ -1697,6 +2243,7 @@ async function launchBrowser() {
1697
2243
  trendingTabBox.visible = true;
1698
2244
  } else {
1699
2245
  searchBox.visible = true;
2246
+ gapBox.visible = true;
1700
2247
  toolbarText.visible = true;
1701
2248
  }
1702
2249
  body.visible = true;
@@ -1718,7 +2265,16 @@ async function launchBrowser() {
1718
2265
  updateDim.visible = false;
1719
2266
  if (type === "none") {
1720
2267
  currentOverlay = "none";
1721
- showMainContent();
2268
+ if (currentMode === "landing") {
2269
+ landingBox.visible = true;
2270
+ body.visible = false;
2271
+ trendingTabBox.visible = false;
2272
+ gapBox.visible = false;
2273
+ searchBox.visible = false;
2274
+ toolbarText.visible = false;
2275
+ } else {
2276
+ showMainContent();
2277
+ }
1722
2278
  renderer.requestRender();
1723
2279
  return;
1724
2280
  }
@@ -1757,14 +2313,14 @@ async function launchBrowser() {
1757
2313
  renderer.requestRender();
1758
2314
  }
1759
2315
  const helpSections = buildHelpSections();
1760
- const helpBox = new BoxRenderable2(renderer, {
2316
+ const helpBox = new BoxRenderable3(renderer, {
1761
2317
  visible: false,
1762
2318
  flexGrow: 1,
1763
2319
  backgroundColor: colors.bg,
1764
2320
  borderBottom: true,
1765
2321
  borderBottomColor: colors.border
1766
2322
  });
1767
- const helpScroll = new ScrollBoxRenderable2(renderer, {
2323
+ const helpScroll = new ScrollBoxRenderable3(renderer, {
1768
2324
  flexGrow: 1,
1769
2325
  backgroundColor: colors.bg,
1770
2326
  scrollY: true,
@@ -1779,7 +2335,7 @@ async function launchBrowser() {
1779
2335
  }
1780
2336
  });
1781
2337
  for (const section of helpSections) {
1782
- const sectionBox = new BoxRenderable2(renderer, {
2338
+ const sectionBox = new BoxRenderable3(renderer, {
1783
2339
  borderBottom: true,
1784
2340
  borderBottomColor: colors.border,
1785
2341
  title: section.title,
@@ -1793,7 +2349,7 @@ async function launchBrowser() {
1793
2349
  for (const row of section.rows) {
1794
2350
  const keys = row.keys.padEnd(HELP_KEYS_COLUMN);
1795
2351
  sectionBox.add(
1796
- new TextRenderable2(renderer, {
2352
+ new TextRenderable3(renderer, {
1797
2353
  content: ` ${keys} ${row.action}`,
1798
2354
  color: colors.text
1799
2355
  })
@@ -1801,7 +2357,7 @@ async function launchBrowser() {
1801
2357
  }
1802
2358
  if (section.note) {
1803
2359
  sectionBox.add(
1804
- new TextRenderable2(renderer, {
2360
+ new TextRenderable3(renderer, {
1805
2361
  content: ` ${section.note}`,
1806
2362
  color: colors.muted
1807
2363
  })
@@ -1811,7 +2367,7 @@ async function launchBrowser() {
1811
2367
  }
1812
2368
  helpBox.add(helpScroll);
1813
2369
  root.add(helpBox);
1814
- const historyBox = new BoxRenderable2(renderer, {
2370
+ const historyBox = new BoxRenderable3(renderer, {
1815
2371
  visible: false,
1816
2372
  flexGrow: 1,
1817
2373
  backgroundColor: colors.bg,
@@ -1848,7 +2404,7 @@ async function launchBrowser() {
1848
2404
  }
1849
2405
  historySelect.setSelectedIndex(0);
1850
2406
  }
1851
- const bookmarksBox = new BoxRenderable2(renderer, {
2407
+ const bookmarksBox = new BoxRenderable3(renderer, {
1852
2408
  visible: false,
1853
2409
  flexGrow: 1,
1854
2410
  backgroundColor: colors.bg,
@@ -1885,7 +2441,7 @@ async function launchBrowser() {
1885
2441
  }
1886
2442
  bookmarksSelect.setSelectedIndex(0);
1887
2443
  }
1888
- const savedBox = new BoxRenderable2(renderer, {
2444
+ const savedBox = new BoxRenderable3(renderer, {
1889
2445
  visible: false,
1890
2446
  flexGrow: 1,
1891
2447
  backgroundColor: colors.bg,
@@ -1907,7 +2463,7 @@ async function launchBrowser() {
1907
2463
  });
1908
2464
  savedBox.add(savedSelect);
1909
2465
  root.add(savedBox);
1910
- const topicsBox = new BoxRenderable2(renderer, {
2466
+ const topicsBox = new BoxRenderable3(renderer, {
1911
2467
  visible: false,
1912
2468
  flexGrow: 1,
1913
2469
  backgroundColor: colors.bg,
@@ -1932,10 +2488,10 @@ async function launchBrowser() {
1932
2488
  async function refreshTopics() {
1933
2489
  try {
1934
2490
  const topics = await fetchTopics();
1935
- topicsSelect.options = topics.map((t2) => ({
1936
- name: `${t2.name} (${t2.repoCount.toLocaleString()} repos)`,
1937
- description: t2.description.slice(0, 80),
1938
- value: t2
2491
+ topicsSelect.options = topics.map((t3) => ({
2492
+ name: `${t3.name} (${t3.repoCount.toLocaleString()} repos)`,
2493
+ description: t3.description.slice(0, 80),
2494
+ value: t3
1939
2495
  }));
1940
2496
  } catch {
1941
2497
  topicsSelect.options = [
@@ -1944,7 +2500,7 @@ async function launchBrowser() {
1944
2500
  }
1945
2501
  topicsSelect.setSelectedIndex(0);
1946
2502
  }
1947
- const exportBox = new BoxRenderable2(renderer, {
2503
+ const exportBox = new BoxRenderable3(renderer, {
1948
2504
  visible: false,
1949
2505
  flexGrow: 1,
1950
2506
  backgroundColor: colors.bg,
@@ -1983,7 +2539,7 @@ async function launchBrowser() {
1983
2539
  });
1984
2540
  exportBox.add(exportSelect);
1985
2541
  root.add(exportBox);
1986
- const compareBox = new BoxRenderable2(renderer, {
2542
+ const compareBox = new BoxRenderable3(renderer, {
1987
2543
  visible: false,
1988
2544
  flexGrow: 1,
1989
2545
  backgroundColor: colors.bg,
@@ -1992,14 +2548,14 @@ async function launchBrowser() {
1992
2548
  title: " Comparison ",
1993
2549
  titleColor: colors.yellow
1994
2550
  });
1995
- const compareText = new TextRenderable2(renderer, {
2551
+ const compareText = new TextRenderable3(renderer, {
1996
2552
  content: "",
1997
2553
  color: colors.text,
1998
2554
  backgroundColor: colors.bg
1999
2555
  });
2000
2556
  compareBox.add(compareText);
2001
2557
  root.add(compareBox);
2002
- const notifsBox = new BoxRenderable2(renderer, {
2558
+ const notifsBox = new BoxRenderable3(renderer, {
2003
2559
  visible: false,
2004
2560
  flexGrow: 1,
2005
2561
  backgroundColor: colors.bg,
@@ -2008,7 +2564,7 @@ async function launchBrowser() {
2008
2564
  title: " Notifications ",
2009
2565
  titleColor: colors.yellow
2010
2566
  });
2011
- const notifsText = new TextRenderable2(renderer, {
2567
+ const notifsText = new TextRenderable3(renderer, {
2012
2568
  content: "(no notifications)",
2013
2569
  color: colors.text,
2014
2570
  backgroundColor: colors.bg
@@ -2023,7 +2579,7 @@ async function launchBrowser() {
2023
2579
  notifsText.content = notifs2.map((n) => ` ${n.icon} ${n.message}`).join("\n");
2024
2580
  }
2025
2581
  }
2026
- const shareBox = new BoxRenderable2(renderer, {
2582
+ const shareBox = new BoxRenderable3(renderer, {
2027
2583
  visible: false,
2028
2584
  flexGrow: 1,
2029
2585
  backgroundColor: colors.bg,
@@ -2084,7 +2640,7 @@ async function launchBrowser() {
2084
2640
  }
2085
2641
  savedSelect.setSelectedIndex(0);
2086
2642
  }
2087
- const leaderDim = new BoxRenderable2(renderer, {
2643
+ const leaderDim = new BoxRenderable3(renderer, {
2088
2644
  visible: false,
2089
2645
  position: "absolute",
2090
2646
  top: 0,
@@ -2095,7 +2651,7 @@ async function launchBrowser() {
2095
2651
  // 60% opacity black (hex alpha: 0x99 ≈ 153/255)
2096
2652
  });
2097
2653
  root.add(leaderDim);
2098
- const leaderBox = new BoxRenderable2(renderer, {
2654
+ const leaderBox = new BoxRenderable3(renderer, {
2099
2655
  visible: false,
2100
2656
  position: "absolute",
2101
2657
  width: "80%",
@@ -2127,7 +2683,7 @@ async function launchBrowser() {
2127
2683
  selectedDescriptionColor: colors.selectionText,
2128
2684
  itemSpacing: 0
2129
2685
  });
2130
- const leaderFooter = new TextRenderable2(renderer, {
2686
+ const leaderFooter = new TextRenderable3(renderer, {
2131
2687
  content: " \u2191\u2193 select Enter run Esc/q close ",
2132
2688
  color: colors.muted,
2133
2689
  backgroundColor: colors.surfaceDim,
@@ -2140,7 +2696,7 @@ async function launchBrowser() {
2140
2696
  leaderBox.add(leaderFooter);
2141
2697
  root.add(leaderDim);
2142
2698
  root.add(leaderBox);
2143
- const updateDim = new BoxRenderable2(renderer, {
2699
+ const updateDim = new BoxRenderable3(renderer, {
2144
2700
  visible: false,
2145
2701
  position: "absolute",
2146
2702
  top: 0,
@@ -2150,7 +2706,7 @@ async function launchBrowser() {
2150
2706
  backgroundColor: "#00000088"
2151
2707
  });
2152
2708
  root.add(updateDim);
2153
- const updateBox = new BoxRenderable2(renderer, {
2709
+ const updateBox = new BoxRenderable3(renderer, {
2154
2710
  visible: false,
2155
2711
  position: "absolute",
2156
2712
  width: "55%",
@@ -2169,20 +2725,20 @@ async function launchBrowser() {
2169
2725
  paddingLeft: 2,
2170
2726
  paddingRight: 2
2171
2727
  });
2172
- const updateText = new TextRenderable2(renderer, {
2728
+ const updateText = new TextRenderable3(renderer, {
2173
2729
  content: "",
2174
2730
  color: colors.accent,
2175
2731
  backgroundColor: colors.surfaceDim
2176
2732
  });
2177
2733
  let updateSelectedOption = 0;
2178
- const updateOptionsRow = new BoxRenderable2(renderer, {
2734
+ const updateOptionsRow = new BoxRenderable3(renderer, {
2179
2735
  flexDirection: "row",
2180
2736
  gap: 1,
2181
2737
  backgroundColor: colors.surfaceDim,
2182
2738
  height: 3,
2183
2739
  width: "100%"
2184
2740
  });
2185
- const updateNowBox = new BoxRenderable2(renderer, {
2741
+ const updateNowBox = new BoxRenderable3(renderer, {
2186
2742
  border: true,
2187
2743
  borderColor: updateSelectedOption === 0 ? colors.yellow : colors.border,
2188
2744
  backgroundColor: colors.surfaceDim,
@@ -2191,13 +2747,13 @@ async function launchBrowser() {
2191
2747
  paddingLeft: 1,
2192
2748
  paddingRight: 1
2193
2749
  });
2194
- const updateNowText = new TextRenderable2(renderer, {
2750
+ const updateNowText = new TextRenderable3(renderer, {
2195
2751
  content: " Update Now ",
2196
2752
  color: updateSelectedOption === 0 ? colors.yellow : colors.text,
2197
2753
  backgroundColor: colors.surfaceDim
2198
2754
  });
2199
2755
  updateNowBox.add(updateNowText);
2200
- const laterBox = new BoxRenderable2(renderer, {
2756
+ const laterBox = new BoxRenderable3(renderer, {
2201
2757
  border: true,
2202
2758
  borderColor: updateSelectedOption === 1 ? colors.yellow : colors.border,
2203
2759
  backgroundColor: colors.surfaceDim,
@@ -2206,13 +2762,13 @@ async function launchBrowser() {
2206
2762
  paddingLeft: 1,
2207
2763
  paddingRight: 1
2208
2764
  });
2209
- const laterText = new TextRenderable2(renderer, {
2765
+ const laterText = new TextRenderable3(renderer, {
2210
2766
  content: " Later",
2211
2767
  color: updateSelectedOption === 1 ? colors.yellow : colors.text,
2212
2768
  backgroundColor: colors.surfaceDim
2213
2769
  });
2214
2770
  laterBox.add(laterText);
2215
- const neverBox = new BoxRenderable2(renderer, {
2771
+ const neverBox = new BoxRenderable3(renderer, {
2216
2772
  border: true,
2217
2773
  borderColor: updateSelectedOption === 2 ? colors.yellow : colors.border,
2218
2774
  backgroundColor: colors.surfaceDim,
@@ -2221,7 +2777,7 @@ async function launchBrowser() {
2221
2777
  paddingLeft: 1,
2222
2778
  paddingRight: 1
2223
2779
  });
2224
- const neverText = new TextRenderable2(renderer, {
2780
+ const neverText = new TextRenderable3(renderer, {
2225
2781
  content: " Don't show again",
2226
2782
  color: updateSelectedOption === 2 ? colors.yellow : colors.text,
2227
2783
  backgroundColor: colors.surfaceDim
@@ -2238,7 +2794,7 @@ async function launchBrowser() {
2238
2794
  }
2239
2795
  updateOptionsRow.add(updateNowBox);
2240
2796
  updateOptionsRow.add(laterBox);
2241
- const updateFooter = new TextRenderable2(renderer, {
2797
+ const updateFooter = new TextRenderable3(renderer, {
2242
2798
  content: " \u2190\u2192 select Enter Esc/q close",
2243
2799
  color: colors.muted,
2244
2800
  backgroundColor: colors.surfaceDim,
@@ -2331,6 +2887,29 @@ async function launchBrowser() {
2331
2887
  }
2332
2888
  }
2333
2889
  );
2890
+ } else if (currentMode === "packages") {
2891
+ searchItems.push(
2892
+ {
2893
+ type: "action",
2894
+ name: "Sort",
2895
+ description: `Cycle package sort (current: ${currentPackageSort})`,
2896
+ action: () => changePackageSort()
2897
+ },
2898
+ {
2899
+ type: "action",
2900
+ name: "Limit",
2901
+ description: `Cycle result limit (current: ${currentLimit})`,
2902
+ action: () => changeLimit()
2903
+ },
2904
+ {
2905
+ type: "action",
2906
+ name: "Refresh",
2907
+ description: "Re-run current package search",
2908
+ action: () => {
2909
+ if (currentQueryInput) doPackageSearch(currentQueryInput);
2910
+ }
2911
+ }
2912
+ );
2334
2913
  }
2335
2914
  if (searchItems.length > 0) {
2336
2915
  groups.push({
@@ -2407,6 +2986,15 @@ async function launchBrowser() {
2407
2986
  });
2408
2987
  }
2409
2988
  const navItems = [];
2989
+ navItems.push({
2990
+ type: "action",
2991
+ name: "Packages",
2992
+ description: "Search npm packages",
2993
+ action: () => {
2994
+ showOverlay("none");
2995
+ showPackagesMode();
2996
+ }
2997
+ });
2410
2998
  if (currentMode === "search") {
2411
2999
  navItems.push({
2412
3000
  type: "action",
@@ -2634,7 +3222,7 @@ async function launchBrowser() {
2634
3222
  renderer.requestRender();
2635
3223
  }
2636
3224
  }
2637
- const readmeBox = new BoxRenderable2(renderer, {
3225
+ const readmeBox = new BoxRenderable3(renderer, {
2638
3226
  visible: false,
2639
3227
  flexGrow: 1,
2640
3228
  backgroundColor: colors.surface,
@@ -2643,7 +3231,7 @@ async function launchBrowser() {
2643
3231
  title: " README ",
2644
3232
  titleColor: colors.green
2645
3233
  });
2646
- const readmeScroll = new ScrollBoxRenderable2(renderer, {
3234
+ const readmeScroll = new ScrollBoxRenderable3(renderer, {
2647
3235
  flexGrow: 1,
2648
3236
  backgroundColor: colors.bg,
2649
3237
  scrollY: true,
@@ -2657,14 +3245,14 @@ async function launchBrowser() {
2657
3245
  width: 1
2658
3246
  }
2659
3247
  });
2660
- const readmeText = new TextRenderable2(renderer, {
3248
+ const readmeText = new TextRenderable3(renderer, {
2661
3249
  content: "",
2662
3250
  color: colors.text,
2663
3251
  backgroundColor: colors.bg
2664
3252
  });
2665
3253
  readmeScroll.add(readmeText);
2666
3254
  readmeBox.add(readmeScroll);
2667
- const readmeFooter = new TextRenderable2(renderer, {
3255
+ const readmeFooter = new TextRenderable3(renderer, {
2668
3256
  content: " \u2191\u2193/jk scroll Esc/q close",
2669
3257
  color: colors.muted,
2670
3258
  backgroundColor: colors.bg,
@@ -2710,7 +3298,7 @@ async function launchBrowser() {
2710
3298
  }
2711
3299
  renderer.requestRender();
2712
3300
  }
2713
- const statusBar = new TextRenderable2(renderer, {
3301
+ const statusBar = new TextRenderable3(renderer, {
2714
3302
  content: " Ready. Press Enter to search.",
2715
3303
  backgroundColor: colors.surface,
2716
3304
  color: colors.muted,
@@ -2735,7 +3323,8 @@ async function launchBrowser() {
2735
3323
  statusMgr.set("idle", msg);
2736
3324
  }
2737
3325
  function setToolbar() {
2738
- toolbarText.content = formatToolbar(currentSort, currentLimit, totalCount);
3326
+ const sort = currentMode === "packages" ? currentPackageSort : currentSort;
3327
+ toolbarText.content = formatToolbar(sort, currentLimit, totalCount);
2739
3328
  renderer.requestRender();
2740
3329
  }
2741
3330
  function updateDetail(repo) {
@@ -2845,33 +3434,38 @@ async function launchBrowser() {
2845
3434
  }
2846
3435
  const xAxis = ` \u2506${"\u2500".repeat(termW)}`;
2847
3436
  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];
3437
+ return [...lines, xAxis, weekLabels];
2849
3438
  }
2850
3439
  async function loadChart(repo) {
2851
3440
  detailText.content = " Loading chart...";
2852
3441
  renderer.requestRender();
2853
3442
  try {
3443
+ const headers = { "User-Agent": "ghfind/1.0" };
3444
+ if (githubToken) headers.Authorization = `Bearer ${githubToken}`;
2854
3445
  const [chartRes, repoRes] = await Promise.all([
2855
3446
  fetch(
2856
3447
  `https://api.github.com/repos/${repo.owner}/${repo.name}/stats/participation`,
2857
- { headers: { "User-Agent": "ghfind/1.0" } }
3448
+ { headers }
2858
3449
  ),
2859
3450
  fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`, {
2860
- headers: { "User-Agent": "ghfind/1.0" }
2861
- }).then((r) => r.ok ? r.json() : null)
3451
+ headers
3452
+ }).then((r) => r.ok ? r.clone().json() : null)
2862
3453
  ]);
2863
3454
  let chartSection = "";
2864
3455
  if (chartRes.ok) {
2865
3456
  const data = await chartRes.json();
2866
3457
  if (data?.all && data.all.length >= 2) {
2867
3458
  chartCommitData = data.all;
2868
- const chartW = 60;
3459
+ const termW = graphFullscreen ? (process.stdout.columns || 120) - 12 : Math.floor((process.stdout.columns || 120) / 2) - 12;
3460
+ const chartW = Math.max(20, Math.min(60, termW));
2869
3461
  const chartH = 10;
2870
3462
  const chartLines = buildChartString(chartCommitData, chartW, chartH);
2871
- chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join(
2872
- "\n"
2873
- );
3463
+ chartSection = ["", "Weekly commits (52 weeks)", ...chartLines].join("\n");
3464
+ } else if (data?.message) {
3465
+ chartSection = ["", `Chart unavailable: ${data.message}`].join("\n");
2874
3466
  }
3467
+ } else {
3468
+ chartSection = ["", `Chart unavailable (API ${chartRes.status})`].join("\n");
2875
3469
  }
2876
3470
  const desc = repo.description ?? "";
2877
3471
  const lang = repo.language ?? "\u2014";
@@ -2982,12 +3576,69 @@ async function launchBrowser() {
2982
3576
  function showSearchMode() {
2983
3577
  currentMode = "search";
2984
3578
  trendingTabBox.visible = false;
3579
+ gapBox.visible = false;
2985
3580
  searchBox.visible = true;
2986
3581
  toolbarText.visible = true;
3582
+ body.visible = true;
2987
3583
  searchInput.placeholder = "Search GitHub repos (e.g. rust cli, or language:Rust stars:>100)";
2988
3584
  searchInput.focus();
2989
3585
  renderer.requestRender();
2990
3586
  }
3587
+ function showPackagesMode() {
3588
+ currentMode = "packages";
3589
+ trendingTabBox.visible = false;
3590
+ gapBox.visible = false;
3591
+ searchBox.visible = true;
3592
+ toolbarText.visible = true;
3593
+ body.visible = true;
3594
+ searchInput.placeholder = "Search npm packages (e.g. react, vue, typescript)";
3595
+ searchInput.focus();
3596
+ packages = [];
3597
+ resultsSelect.options = [
3598
+ { name: "", description: "Type a query and press Enter to search", value: null }
3599
+ ];
3600
+ detailText.content = "";
3601
+ renderer.requestRender();
3602
+ }
3603
+ async function doPackageSearch(queryText) {
3604
+ const q = queryText.trim();
3605
+ if (q === "" || isLoading) return;
3606
+ currentQueryInput = q;
3607
+ isLoading = true;
3608
+ resultsSelect.options = [
3609
+ { name: " Searching packages...", description: "", value: null }
3610
+ ];
3611
+ detailText.content = "";
3612
+ statusMgr.set("searching", `Searching npm for "${q}"`);
3613
+ renderer.requestRender();
3614
+ try {
3615
+ const search = createPackageSearch();
3616
+ const result = await search.searchPackage(q, currentLimit);
3617
+ totalCount = result.totalCount;
3618
+ packageResultsRaw = result.packages;
3619
+ packages = sortPackages(packageResultsRaw, currentPackageSort);
3620
+ setToolbar();
3621
+ if (packages.length > 0) {
3622
+ resultsSelect.options = packages.map((p) => ({
3623
+ name: `${p.name}@${p.version}`,
3624
+ description: p.description ?? "",
3625
+ value: p
3626
+ }));
3627
+ resultsSelect.setSelectedIndex(0);
3628
+ statusMgr.set("success", `${packages.length} packages found`);
3629
+ } else {
3630
+ throw new NoResultsError(q);
3631
+ }
3632
+ } catch (err) {
3633
+ const msg = err instanceof Error ? err.message : String(err);
3634
+ resultsSelect.options = [
3635
+ { name: " (error)", description: "", value: null }
3636
+ ];
3637
+ statusMgr.set("error", msg.slice(0, 60));
3638
+ }
3639
+ isLoading = false;
3640
+ renderer.requestRender();
3641
+ }
2991
3642
  async function doSearch(queryText, append = false) {
2992
3643
  const q = queryText.trim();
2993
3644
  if (q === "" || isLoading) return;
@@ -3074,6 +3725,22 @@ async function launchBrowser() {
3074
3725
  renderer.requestRender();
3075
3726
  }
3076
3727
  }
3728
+ function changePackageSort() {
3729
+ const idx = PACKAGE_SORT_MODES.findIndex((m) => m.key === currentPackageSort);
3730
+ const next = PACKAGE_SORT_MODES[(idx + 1) % PACKAGE_SORT_MODES.length];
3731
+ currentPackageSort = next.key;
3732
+ setToolbar();
3733
+ if (packageResultsRaw.length > 0) {
3734
+ packages = sortPackages(packageResultsRaw, currentPackageSort);
3735
+ resultsSelect.options = packages.map((p) => ({
3736
+ name: `${p.name}@${p.version}`,
3737
+ description: p.description ?? "",
3738
+ value: p
3739
+ }));
3740
+ resultsSelect.setSelectedIndex(0);
3741
+ renderer.requestRender();
3742
+ }
3743
+ }
3077
3744
  function changeLimit() {
3078
3745
  const limits = [10, 25, 50, 100];
3079
3746
  const idx = limits.indexOf(currentLimit);
@@ -3084,22 +3751,40 @@ async function launchBrowser() {
3084
3751
  }
3085
3752
  searchInput.on("enter", () => {
3086
3753
  if (searchInput.value.trim() === "" || isLoading) return;
3087
- showSearchMode();
3088
- doSearch(searchInput.value);
3754
+ if (currentMode === "packages") {
3755
+ doPackageSearch(searchInput.value);
3756
+ } else {
3757
+ doSearch(searchInput.value);
3758
+ }
3089
3759
  });
3090
3760
  resultsSelect.on("selectionChanged", () => {
3091
3761
  const opt = resultsSelect.getSelectedOption();
3092
- const repo = opt?.value;
3093
- if (repo) {
3094
- if (graphFullscreen) loadChart(repo);
3095
- else updateDetail(repo);
3762
+ if (currentMode === "packages") {
3763
+ const pack = opt?.value;
3764
+ if (pack) {
3765
+ detailText.content = `Package: ${pack.name}@${pack.version}
3766
+ Downloads: ${pack.downloads.toLocaleString()}
3767
+ Score: ${pack.score.toFixed(2)}
3768
+ ${pack.description ?? ""}`;
3769
+ }
3770
+ } else {
3771
+ const repo = opt?.value;
3772
+ if (repo) {
3773
+ if (graphFullscreen) loadChart(repo);
3774
+ else updateDetail(repo);
3775
+ }
3096
3776
  }
3097
3777
  renderer.requestRender();
3098
3778
  });
3099
3779
  resultsSelect.on("itemSelected", () => {
3100
3780
  const opt = resultsSelect.getSelectedOption();
3101
- const repo = opt?.value;
3102
- if (repo) openUrl(repo.url);
3781
+ if (currentMode === "packages") {
3782
+ const pack = opt?.value;
3783
+ if (pack?.url) openUrl(pack.url);
3784
+ } else {
3785
+ const repo = opt?.value;
3786
+ if (repo) openUrl(repo.url);
3787
+ }
3103
3788
  });
3104
3789
  renderer.keyInput.on("keypress", (key) => {
3105
3790
  if (currentOverlay !== "none") {
@@ -3389,6 +4074,18 @@ async function launchBrowser() {
3389
4074
  showLeaderMenu();
3390
4075
  }
3391
4076
  return;
4077
+ if (key.name === "t" && !searchInput.focused) {
4078
+ const current = config.theme;
4079
+ const themes = listThemes();
4080
+ const idx = themes.indexOf(current);
4081
+ const next = themes[(idx + 1) % themes.length];
4082
+ const nextTheme = loadTheme(next);
4083
+ Object.assign(colors, nextTheme);
4084
+ config.theme = next;
4085
+ saveConfig(config);
4086
+ renderer.requestRender();
4087
+ return;
4088
+ }
3392
4089
  }
3393
4090
  if (key.name === "?" || key.name === "h" && key.ctrl) {
3394
4091
  showOverlay("help");
@@ -3471,18 +4168,23 @@ async function launchBrowser() {
3471
4168
  currentVersion = pkg.version;
3472
4169
  } catch {
3473
4170
  }
4171
+ if (!session || !session.mode) {
4172
+ showLanding(true);
4173
+ }
3474
4174
  checkForUpdateAndShow();
3475
4175
  renderer.start();
3476
- if (currentMode === "trending") {
3477
- loadTrending();
3478
- } else {
3479
- searchInput.value = currentQueryInput;
3480
- if (currentQueryInput) {
3481
- doSearch(currentQueryInput);
4176
+ if (session && session.mode) {
4177
+ if (currentMode === "trending") {
4178
+ loadTrending();
3482
4179
  } else {
3483
- showSearchMode();
4180
+ searchInput.value = currentQueryInput;
4181
+ if (currentQueryInput) {
4182
+ doSearch(currentQueryInput);
4183
+ } else {
4184
+ showSearchMode();
4185
+ }
4186
+ searchInput.focus();
3484
4187
  }
3485
- searchInput.focus();
3486
4188
  }
3487
4189
  renderer.requestRender();
3488
4190
  }
@@ -3513,6 +4215,7 @@ if (import.meta.main && false) {
3513
4215
  }
3514
4216
 
3515
4217
  // src/cli.ts
4218
+ process.env.GHFIND_CLI_RUN = "1";
3516
4219
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
3517
4220
  var PKG_DIR2 = join3(__dirname2, "..");
3518
4221
  function parseArgs(args) {
@@ -3531,7 +4234,9 @@ function parseArgs(args) {
3531
4234
  interval: 300,
3532
4235
  init: false,
3533
4236
  version: false,
3534
- help: false
4237
+ help: false,
4238
+ pkg: false,
4239
+ registry: "npm"
3535
4240
  };
3536
4241
  let queryParts = [];
3537
4242
  for (let i = 0; i < args.length; i++) {
@@ -3591,8 +4296,11 @@ function parseArgs(args) {
3591
4296
  case "--format":
3592
4297
  flags.format = args[++i];
3593
4298
  break;
3594
- case "--interval":
3595
- flags.interval = parseInt(args[++i]) || 300;
4299
+ case "--registry":
4300
+ flags.registry = args[++i] ?? "npm";
4301
+ break;
4302
+ case "--theme":
4303
+ flags.theme = args[++i] ?? "tokyo-night";
3596
4304
  break;
3597
4305
  case "--completion":
3598
4306
  flags.completion = args[++i];
@@ -3601,6 +4309,10 @@ function parseArgs(args) {
3601
4309
  if (!arg.startsWith("-")) queryParts.push(arg);
3602
4310
  }
3603
4311
  }
4312
+ if (queryParts[0] === "pkg") {
4313
+ flags.pkg = true;
4314
+ queryParts.shift();
4315
+ }
3604
4316
  flags.query = queryParts.join(" ");
3605
4317
  return flags;
3606
4318
  }
@@ -3624,6 +4336,8 @@ Usage:
3624
4336
  ghfind <query> --format <fmt> Format lines (urls|names|ssh-urls|clone-commands|ids)
3625
4337
  ghfind <query> --pipe <target> Pipe to clone/open
3626
4338
  ghfind --trending --json Trending repos as JSON
4339
+ ghfind pkg <query> --json Search npm packages, output JSON
4340
+ ghfind pkg <query> Search npm packages, text list
3627
4341
  ghfind --watch <query> Watch mode (poll every Ns)
3628
4342
  ghfind init Run setup wizard
3629
4343
  ghfind --completion <shell> Print completion script (bash|zsh|fish)
@@ -3634,11 +4348,13 @@ Options:
3634
4348
  --json, --csv, --markdown, --count Output format
3635
4349
  --limit <n> Max results (default: 50)
3636
4350
  --sort <strategy> Sort: best-match|stars|updated|forks
3637
- --token <token> GitHub API token
4351
+ --theme <name> Theme: tokyo-night|premium-dark (default: tokyo-night)
3638
4352
  --trending Trending mode
3639
4353
  --since <period> Trending period: daily|weekly|monthly
3640
4354
  --pipe <target> Pipe target: clone|open
3641
4355
  --format <fmt> Line format: urls|names|ssh-urls|...
4356
+ --registry <name> Registry for pkg: npm (default) | others TBD
4357
+
3642
4358
  --watch Watch mode (re-run periodically)
3643
4359
  --interval <s> Watch interval in seconds (default: 300)
3644
4360
  --completion <shell> Generate completions
@@ -3660,13 +4376,22 @@ Options:
3660
4376
  await runInitWizard();
3661
4377
  return;
3662
4378
  }
4379
+ if (flags.pkg) {
4380
+ const interactive = !flags.query && !flags.json && !flags.csv && !flags.markdown && !flags.count && !flags.format;
4381
+ if (interactive) {
4382
+ await launchPackageBrowser();
4383
+ } else {
4384
+ await runPackageSearch(flags);
4385
+ }
4386
+ return;
4387
+ }
3663
4388
  const outputFormat = flags.json ? "json" : flags.csv ? "csv" : flags.markdown ? "markdown" : void 0;
3664
4389
  const isNonInteractive = outputFormat || flags.count || flags.format || flags.pipe || flags.watch;
3665
4390
  if (isNonInteractive) {
3666
4391
  await runNonInteractive(flags, outputFormat);
3667
4392
  return;
3668
4393
  }
3669
- await launchBrowser();
4394
+ await launchBrowser(flags.theme);
3670
4395
  }
3671
4396
  function buildSearchContext(flags) {
3672
4397
  const parsed = applyFlagFilters(parseQuery(flags.query), {});
@@ -3676,6 +4401,48 @@ function buildSearchContext(flags) {
3676
4401
  provider: createGitHubSearch(void 0, flags.token ? [flags.token] : [])
3677
4402
  };
3678
4403
  }
4404
+ async function runPackageSearch(flags) {
4405
+ if (flags.registry !== "npm") {
4406
+ console.error(`Registry "${flags.registry}" not yet supported (only npm).`);
4407
+ process.exit(1);
4408
+ }
4409
+ const PKG_SORTS = ["best-match", "score", "downloads", "name"];
4410
+ const pkgSort = flags.sort;
4411
+ if (!PKG_SORTS.includes(pkgSort)) {
4412
+ console.error(`Invalid sort for pkg: "${flags.sort}" (use best-match|score|downloads|name).`);
4413
+ process.exit(1);
4414
+ }
4415
+ const search = createPackageSearch();
4416
+ const query = flags.query.trim();
4417
+ if (!query) {
4418
+ console.error("Usage: ghfind pkg <query> [--json|--csv|--markdown|--count|--format urls|names] [--limit <n>]");
4419
+ process.exit(1);
4420
+ }
4421
+ try {
4422
+ const { totalCount, packages } = await search.searchPackage(query, flags.limit);
4423
+ const sorted = sortPackages(packages, pkgSort);
4424
+ if (flags.count) {
4425
+ console.log(totalCount);
4426
+ } else if (flags.json) {
4427
+ console.log(formatPackagesJson(sorted));
4428
+ } else if (flags.csv) {
4429
+ console.log(formatPackagesCsv(sorted));
4430
+ } else if (flags.markdown) {
4431
+ console.log(formatPackagesMarkdown(sorted));
4432
+ } else if (flags.format === "urls") {
4433
+ console.log(formatPackagesUrls(sorted));
4434
+ } else if (flags.format === "names") {
4435
+ console.log(formatPackagesNames(sorted));
4436
+ } else {
4437
+ for (const p of sorted) {
4438
+ console.log(`${p.name}@${p.version} \u2193 ${p.downloads.toLocaleString()} ${p.score.toFixed(2)} ${p.description ?? ""}`);
4439
+ }
4440
+ }
4441
+ } catch (err) {
4442
+ console.error(err instanceof Error ? err.message : String(err));
4443
+ process.exit(1);
4444
+ }
4445
+ }
3679
4446
  async function runNonInteractive(flags, outputFormat) {
3680
4447
  const handlers = {
3681
4448
  trending: async (ctx2) => {
@@ -3756,6 +4523,7 @@ async function runNonInteractive(flags, outputFormat) {
3756
4523
  await handlers[key](ctx);
3757
4524
  }
3758
4525
  main().catch((err) => {
4526
+ console.error("DEBUG: uncaught error:", err.message || err);
3759
4527
  console.error(err instanceof Error ? err.message : String(err));
3760
4528
  process.exit(1);
3761
4529
  });