github-search-cli 9.4.2 → 9.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -4
- package/README.md +8 -3
- package/bin/ghfind.js +98 -89
- package/completions/ghfind.bash +1 -1
- package/completions/ghfind.fish +3 -1
- package/completions/ghfind.zsh +6 -0
- package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
- package/dist/cli.js +761 -342
- package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
- package/package.json +6 -3
- package/release-notes/9.3.0.md +32 -29
- package/scripts/build.ts +34 -19
- package/scripts/generate-svg-demo.js +218 -168
- package/scripts/postinstall.js +176 -176
- package/src/bookmarks.ts +69 -69
- package/src/cli.ts +17 -1
- package/src/compare.ts +69 -47
- package/src/config.ts +70 -65
- package/src/deepdive.ts +149 -118
- package/src/doctor.ts +287 -287
- package/src/errors.ts +62 -59
- package/src/explore.ts +34 -29
- package/src/export.ts +73 -63
- package/src/help.ts +157 -157
- package/src/history.ts +69 -55
- package/src/init.ts +60 -55
- package/src/landing.ts +106 -104
- package/src/login.ts +186 -0
- package/src/markdown-render.ts +1 -6
- package/src/notifications.ts +75 -59
- package/src/open-url.ts +16 -16
- package/src/org.ts +43 -15
- package/src/output.ts +153 -137
- package/src/package-browser.ts +317 -305
- package/src/package.ts +149 -137
- package/src/pipe.ts +60 -54
- package/src/releases.ts +182 -144
- package/src/saved-searches.ts +72 -50
- package/src/search-state.ts +283 -229
- package/src/search.ts +678 -653
- package/src/session.ts +26 -24
- package/src/share.ts +61 -54
- package/src/status.ts +68 -61
- package/src/storage.ts +136 -106
- package/src/themes.ts +259 -116
- package/src/tips.ts +42 -42
- package/src/toast.ts +70 -69
- package/src/trending-parser.ts +104 -0
- package/src/trending.ts +481 -462
- package/src/tui.ts +149 -153
- package/src/types.ts +160 -158
- package/src/watch.ts +95 -74
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
loadConfig,
|
|
5
5
|
saveConfig,
|
|
6
6
|
stateDir
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GYIL4S5X.js";
|
|
8
8
|
|
|
9
9
|
// src/errors.ts
|
|
10
10
|
var SearchCliError = class extends Error {
|
|
@@ -56,6 +56,73 @@ var ParseError = class extends SearchCliError {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// src/trending-parser.ts
|
|
60
|
+
function flattenHtml(html) {
|
|
61
|
+
return html.replace(/>\s+</g, "><").replace(/\s+/g, " ");
|
|
62
|
+
}
|
|
63
|
+
function parseRepoLink(block) {
|
|
64
|
+
const anchor = block.match(
|
|
65
|
+
/<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([^\"?#]+)"[^>]*class="Link"/
|
|
66
|
+
);
|
|
67
|
+
if (!anchor) return void 0;
|
|
68
|
+
return { owner: anchor[1], name: anchor[2] };
|
|
69
|
+
}
|
|
70
|
+
function parseStars(block) {
|
|
71
|
+
const starsMatch = block.match(
|
|
72
|
+
/href="\/[^\/\"]+\/[^\/\"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/
|
|
73
|
+
);
|
|
74
|
+
return starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
|
|
75
|
+
}
|
|
76
|
+
function parseStarsToday(block) {
|
|
77
|
+
const growthMatch = block.match(/(\d[\d,]*)\s+stars\s+(today|this\s+\w+)/);
|
|
78
|
+
return growthMatch ? parseInt(growthMatch[1].replace(/,/g, ""), 10) : 0;
|
|
79
|
+
}
|
|
80
|
+
function parseLanguage(block) {
|
|
81
|
+
const langMatch = block.match(/itemprop="programmingLanguage">([^<]+)</);
|
|
82
|
+
return langMatch ? langMatch[1].trim() : "";
|
|
83
|
+
}
|
|
84
|
+
function parseDescription(block) {
|
|
85
|
+
const descMatch = block.match(
|
|
86
|
+
/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([^<]+)</
|
|
87
|
+
);
|
|
88
|
+
return descMatch ? descMatch[1].trim() : "";
|
|
89
|
+
}
|
|
90
|
+
function parseTrendingHtml(html) {
|
|
91
|
+
const flat = flattenHtml(html);
|
|
92
|
+
const repos = [];
|
|
93
|
+
const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
|
|
94
|
+
let match;
|
|
95
|
+
let rank = 0;
|
|
96
|
+
while ((match = articleRe.exec(flat)) !== null) {
|
|
97
|
+
rank++;
|
|
98
|
+
const block = match[1];
|
|
99
|
+
const link = parseRepoLink(block);
|
|
100
|
+
if (!link) continue;
|
|
101
|
+
repos.push({
|
|
102
|
+
rank,
|
|
103
|
+
owner: link.owner,
|
|
104
|
+
name: link.name,
|
|
105
|
+
stars: parseStars(block),
|
|
106
|
+
starsToday: parseStarsToday(block),
|
|
107
|
+
language: parseLanguage(block),
|
|
108
|
+
description: parseDescription(block)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (rank === 0) {
|
|
112
|
+
throw new ParseError(
|
|
113
|
+
"github.com/trending",
|
|
114
|
+
"no article rows found \u2014 response may be an error page or a rewrite"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (repos.length === 0) {
|
|
118
|
+
throw new ParseError(
|
|
119
|
+
"github.com/trending",
|
|
120
|
+
"article rows present but no repository heading could be parsed"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return repos;
|
|
124
|
+
}
|
|
125
|
+
|
|
59
126
|
// src/search.ts
|
|
60
127
|
var noopLogger = {
|
|
61
128
|
debug: () => {
|
|
@@ -235,15 +302,28 @@ function matchQualifier(token) {
|
|
|
235
302
|
}
|
|
236
303
|
function applyFlagFilters(query, flags) {
|
|
237
304
|
const qualifiers = [...query.qualifiers];
|
|
238
|
-
if (flags.language)
|
|
239
|
-
|
|
240
|
-
if (flags.
|
|
241
|
-
|
|
242
|
-
if (flags.
|
|
305
|
+
if (flags.language)
|
|
306
|
+
qualifiers.push({ key: "language", value: flags.language, negated: false });
|
|
307
|
+
if (flags.stars)
|
|
308
|
+
qualifiers.push({ key: "stars", value: flags.stars, negated: false });
|
|
309
|
+
if (flags.org)
|
|
310
|
+
qualifiers.push({ key: "org", value: flags.org, negated: false });
|
|
311
|
+
if (flags.user)
|
|
312
|
+
qualifiers.push({ key: "user", value: flags.user, negated: false });
|
|
313
|
+
if (flags.topic)
|
|
314
|
+
qualifiers.push({ key: "topic", value: flags.topic, negated: false });
|
|
243
315
|
if (flags.archived !== void 0)
|
|
244
|
-
qualifiers.push({
|
|
316
|
+
qualifiers.push({
|
|
317
|
+
key: "archived",
|
|
318
|
+
value: flags.archived ? "true" : "false",
|
|
319
|
+
negated: false
|
|
320
|
+
});
|
|
245
321
|
if (flags.fork !== void 0)
|
|
246
|
-
qualifiers.push({
|
|
322
|
+
qualifiers.push({
|
|
323
|
+
key: "fork",
|
|
324
|
+
value: flags.fork ? "true" : "false",
|
|
325
|
+
negated: false
|
|
326
|
+
});
|
|
247
327
|
return { ...query, qualifiers };
|
|
248
328
|
}
|
|
249
329
|
function validateQuery(query) {
|
|
@@ -359,12 +439,16 @@ var GitHubSearchAdapter = class {
|
|
|
359
439
|
throw new RateLimitError(!!token);
|
|
360
440
|
}
|
|
361
441
|
const body = await res.text().catch(() => "");
|
|
362
|
-
this.logger.error(
|
|
442
|
+
this.logger.error(
|
|
443
|
+
`[github] non-rate-limit 403: ${body.slice(0, 200)}`
|
|
444
|
+
);
|
|
363
445
|
throw new NetworkError();
|
|
364
446
|
}
|
|
365
447
|
if (!res.ok) {
|
|
366
448
|
const body = await res.text().catch(() => "");
|
|
367
|
-
this.logger.error(
|
|
449
|
+
this.logger.error(
|
|
450
|
+
`[github] API error ${res.status}: ${body.slice(0, 200)}`
|
|
451
|
+
);
|
|
368
452
|
throw new NetworkError();
|
|
369
453
|
}
|
|
370
454
|
const raw = await res.text();
|
|
@@ -374,11 +458,19 @@ var GitHubSearchAdapter = class {
|
|
|
374
458
|
} catch {
|
|
375
459
|
const snippet = raw.slice(0, 200);
|
|
376
460
|
this.logger.error(`[github] invalid JSON response: ${snippet}`);
|
|
377
|
-
throw new ParseError(
|
|
461
|
+
throw new ParseError(
|
|
462
|
+
"GitHub API",
|
|
463
|
+
snippet.includes("<!DOCTYPE") ? "GitHub returned an HTML page (maintenance or CAPTCHA?)" : "Invalid JSON response"
|
|
464
|
+
);
|
|
378
465
|
}
|
|
379
466
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.items)) {
|
|
380
|
-
this.logger.error(
|
|
381
|
-
|
|
467
|
+
this.logger.error(
|
|
468
|
+
`[github] unexpected response shape: ${JSON.stringify(parsed).slice(0, 200)}`
|
|
469
|
+
);
|
|
470
|
+
throw new ParseError(
|
|
471
|
+
"GitHub API",
|
|
472
|
+
"Unexpected response shape \u2014 missing items array"
|
|
473
|
+
);
|
|
382
474
|
}
|
|
383
475
|
const env = parsed;
|
|
384
476
|
totalCount = env.total_count;
|
|
@@ -409,31 +501,6 @@ function appendPage(url, page, perPage) {
|
|
|
409
501
|
return u.toString();
|
|
410
502
|
}
|
|
411
503
|
var TRENDING_URL = "https://github.com/trending";
|
|
412
|
-
function parseTrendingHtml(html) {
|
|
413
|
-
const flat = html.replace(/>\s+</g, "><").replace(/\s+/g, " ");
|
|
414
|
-
const repos = [];
|
|
415
|
-
const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
|
|
416
|
-
let match;
|
|
417
|
-
let rank = 0;
|
|
418
|
-
while ((match = articleRe.exec(flat)) !== null) {
|
|
419
|
-
rank++;
|
|
420
|
-
const block = match[1];
|
|
421
|
-
const repoMatch = block.match(/href="\/([^\/\"]+)\/([^\"?#]+)"[^>]*class="Link"/);
|
|
422
|
-
if (!repoMatch) continue;
|
|
423
|
-
const owner = repoMatch[1];
|
|
424
|
-
const name = repoMatch[2];
|
|
425
|
-
const starsMatch = block.match(/href="\/[^\/\"]+\/[^\/\"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/);
|
|
426
|
-
const stars = starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
|
|
427
|
-
const growthMatch = block.match(/(\d[\d,]*)\s+stars\s+(today|this\s+\w+)/);
|
|
428
|
-
const starsToday = growthMatch ? parseInt(growthMatch[1].replace(/,/g, ""), 10) : 0;
|
|
429
|
-
const langMatch = block.match(/itemprop="programmingLanguage">([^<]+)</);
|
|
430
|
-
const language = langMatch ? langMatch[1].trim() : "";
|
|
431
|
-
const descMatch = block.match(/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([^<]+)</);
|
|
432
|
-
const description = descMatch ? descMatch[1].trim() : "";
|
|
433
|
-
repos.push({ rank, owner, name, stars, starsToday, language, description });
|
|
434
|
-
}
|
|
435
|
-
return repos;
|
|
436
|
-
}
|
|
437
504
|
function trendingRepoToRepo(r) {
|
|
438
505
|
return {
|
|
439
506
|
id: 0,
|
|
@@ -622,6 +689,161 @@ function openUrl(url) {
|
|
|
622
689
|
}
|
|
623
690
|
}
|
|
624
691
|
|
|
692
|
+
// src/themes.ts
|
|
693
|
+
var THEMES = {
|
|
694
|
+
"tokyo-night": {
|
|
695
|
+
name: "tokyo-night",
|
|
696
|
+
// Genuine Tokyo Night palette — cool blue-violet mood
|
|
697
|
+
bg: "#1a1b26",
|
|
698
|
+
surface: "#24283b",
|
|
699
|
+
surfaceAlt: "#292e42",
|
|
700
|
+
text: "#c0caf5",
|
|
701
|
+
muted: "#565f89",
|
|
702
|
+
blue: "#7aa2f7",
|
|
703
|
+
green: "#9ece6a",
|
|
704
|
+
yellow: "#e0af68",
|
|
705
|
+
red: "#f7768e",
|
|
706
|
+
teal: "#73daca",
|
|
707
|
+
purple: "#bb9af7",
|
|
708
|
+
orange: "#ff9e64",
|
|
709
|
+
border: "#24283b",
|
|
710
|
+
borderAlt: "#1f2335",
|
|
711
|
+
separator: "#1f2335",
|
|
712
|
+
selectionBg: "#2d3f76",
|
|
713
|
+
selectionText: "#c0caf5",
|
|
714
|
+
success: "#9ece6a",
|
|
715
|
+
warning: "#e0af68",
|
|
716
|
+
error: "#f7768e",
|
|
717
|
+
surfaceDim: "#16161e",
|
|
718
|
+
accent: "#7aa2f7",
|
|
719
|
+
accentDim: "#5d7abf",
|
|
720
|
+
borderAccent: "#1f2335"
|
|
721
|
+
},
|
|
722
|
+
"premium-dark": {
|
|
723
|
+
name: "premium-dark",
|
|
724
|
+
// Warm counter-mood to Tokyo Night — amber/rust accent
|
|
725
|
+
bg: "#1e1e2e",
|
|
726
|
+
surface: "#262637",
|
|
727
|
+
surfaceAlt: "#2e2e42",
|
|
728
|
+
text: "#cdd6f4",
|
|
729
|
+
muted: "#6c7086",
|
|
730
|
+
blue: "#89b4fa",
|
|
731
|
+
green: "#a6e3a1",
|
|
732
|
+
yellow: "#f9e2af",
|
|
733
|
+
red: "#f38ba8",
|
|
734
|
+
teal: "#94e2d5",
|
|
735
|
+
purple: "#cba6f7",
|
|
736
|
+
orange: "#fab387",
|
|
737
|
+
border: "#313244",
|
|
738
|
+
borderAlt: "#2a2a3d",
|
|
739
|
+
separator: "#2a2a3d",
|
|
740
|
+
selectionBg: "#45475a",
|
|
741
|
+
selectionText: "#cdd6f4",
|
|
742
|
+
success: "#a6e3a1",
|
|
743
|
+
warning: "#f9e2af",
|
|
744
|
+
error: "#f38ba8",
|
|
745
|
+
surfaceDim: "#181825",
|
|
746
|
+
accent: "#f9e2af",
|
|
747
|
+
accentDim: "#c9a87a",
|
|
748
|
+
borderAccent: "#313244"
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function loadTheme(name) {
|
|
752
|
+
return THEMES[name] ?? THEMES["tokyo-night"];
|
|
753
|
+
}
|
|
754
|
+
function listThemes() {
|
|
755
|
+
return Object.keys(THEMES);
|
|
756
|
+
}
|
|
757
|
+
function parseHex(hex) {
|
|
758
|
+
let h = hex.trim().replace(/^#/, "");
|
|
759
|
+
if (h.length === 3)
|
|
760
|
+
h = h.split("").map((c) => c + c).join("");
|
|
761
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
762
|
+
return [
|
|
763
|
+
parseInt(h.slice(0, 2), 16),
|
|
764
|
+
parseInt(h.slice(2, 4), 16),
|
|
765
|
+
parseInt(h.slice(4, 6), 16)
|
|
766
|
+
];
|
|
767
|
+
}
|
|
768
|
+
var toHex2 = (n) => n.toString(16).padStart(2, "0");
|
|
769
|
+
function mixHex(a, b, t3) {
|
|
770
|
+
const ca = parseHex(a);
|
|
771
|
+
const cb = parseHex(b);
|
|
772
|
+
if (!ca || !cb) return a;
|
|
773
|
+
const mix = (x, y) => Math.round(x + (y - x) * t3);
|
|
774
|
+
return `#${toHex2(mix(ca[0], cb[0]))}${toHex2(mix(ca[1], cb[1]))}${toHex2(mix(ca[2], cb[2]))}`;
|
|
775
|
+
}
|
|
776
|
+
function isDarkHex(hex) {
|
|
777
|
+
const c = parseHex(hex);
|
|
778
|
+
if (!c) return false;
|
|
779
|
+
return (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255 < 0.5;
|
|
780
|
+
}
|
|
781
|
+
function deriveSurfaceLayers(bgHex) {
|
|
782
|
+
if (!isDarkHex(bgHex)) return null;
|
|
783
|
+
const w = "#ffffff";
|
|
784
|
+
return {
|
|
785
|
+
surface: mixHex(bgHex, w, 0.045),
|
|
786
|
+
surfaceAlt: mixHex(bgHex, w, 0.07),
|
|
787
|
+
// Sunken modal/overlay panels (toast etc.) — darker than the backdrop
|
|
788
|
+
surfaceDim: mixHex(bgHex, "#000000", 0.15),
|
|
789
|
+
border: mixHex(bgHex, w, 0.09),
|
|
790
|
+
borderAlt: mixHex(bgHex, w, 0.055),
|
|
791
|
+
separator: mixHex(bgHex, w, 0.055),
|
|
792
|
+
borderAccent: mixHex(bgHex, w, 0.065)
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function parseOscColor(spec) {
|
|
796
|
+
const m = spec.trim().match(
|
|
797
|
+
/^(?:rgb|rgba):([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})(?:\/[0-9a-fA-F]{1,4})?$/
|
|
798
|
+
);
|
|
799
|
+
if (!m) return null;
|
|
800
|
+
const chan = (h) => {
|
|
801
|
+
const value = parseInt(h, 16);
|
|
802
|
+
const max = 16 ** h.length - 1;
|
|
803
|
+
return Math.round(value * 255 / max);
|
|
804
|
+
};
|
|
805
|
+
const hex = (n) => n.toString(16).padStart(2, "0");
|
|
806
|
+
return `#${hex(chan(m[1]))}${hex(chan(m[2]))}${hex(chan(m[3]))}`;
|
|
807
|
+
}
|
|
808
|
+
async function detectTerminalBackground(timeoutMs = 400) {
|
|
809
|
+
const stdin = process.stdin;
|
|
810
|
+
const stdout = process.stdout;
|
|
811
|
+
const setRawMode = stdin.setRawMode;
|
|
812
|
+
if (!stdout.isTTY || !stdin.isTTY || typeof setRawMode !== "function")
|
|
813
|
+
return null;
|
|
814
|
+
return new Promise((resolve) => {
|
|
815
|
+
const wasRaw = stdin.isRaw === true;
|
|
816
|
+
let settled = false;
|
|
817
|
+
let buffer = "";
|
|
818
|
+
const finish = (result) => {
|
|
819
|
+
if (settled) return;
|
|
820
|
+
settled = true;
|
|
821
|
+
clearTimeout(timer);
|
|
822
|
+
stdin.removeListener("data", onData);
|
|
823
|
+
try {
|
|
824
|
+
setRawMode.call(stdin, wasRaw);
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
stdin.pause();
|
|
828
|
+
resolve(result);
|
|
829
|
+
};
|
|
830
|
+
const onData = (d) => {
|
|
831
|
+
buffer += d.toString("latin1");
|
|
832
|
+
const m = buffer.match(/\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)/);
|
|
833
|
+
if (m) finish(parseOscColor(m[1]));
|
|
834
|
+
};
|
|
835
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
836
|
+
try {
|
|
837
|
+
setRawMode.call(stdin, true);
|
|
838
|
+
stdin.on("data", onData);
|
|
839
|
+
stdin.resume();
|
|
840
|
+
stdout.write("\x1B]11;?\x07");
|
|
841
|
+
} catch {
|
|
842
|
+
finish(null);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
625
847
|
// src/package-browser.ts
|
|
626
848
|
var C = {
|
|
627
849
|
bg: "#1a1b26",
|
|
@@ -660,6 +882,11 @@ function formatPackageLine(pack) {
|
|
|
660
882
|
return new StyledText(chunks);
|
|
661
883
|
}
|
|
662
884
|
async function launchPackageBrowser() {
|
|
885
|
+
const terminalBg = await detectTerminalBackground();
|
|
886
|
+
if (terminalBg) {
|
|
887
|
+
C.bg = terminalBg;
|
|
888
|
+
Object.assign(C, deriveSurfaceLayers(terminalBg));
|
|
889
|
+
}
|
|
663
890
|
let renderer;
|
|
664
891
|
try {
|
|
665
892
|
renderer = await createCliRenderer();
|
|
@@ -801,7 +1028,9 @@ async function launchPackageBrowser() {
|
|
|
801
1028
|
isLoading = true;
|
|
802
1029
|
dirty = false;
|
|
803
1030
|
clearRows();
|
|
804
|
-
setStatus({
|
|
1031
|
+
setStatus({
|
|
1032
|
+
content: t`${dim(fg(C.muted)(` Loading packages for "${q}"...`))}`
|
|
1033
|
+
});
|
|
805
1034
|
renderer.requestRender();
|
|
806
1035
|
try {
|
|
807
1036
|
const searcher = createPackageSearch();
|
|
@@ -1147,7 +1376,7 @@ var cached = null;
|
|
|
1147
1376
|
function getVersion() {
|
|
1148
1377
|
if (cached) return cached;
|
|
1149
1378
|
if (true) {
|
|
1150
|
-
cached = "9.4.
|
|
1379
|
+
cached = "9.4.3";
|
|
1151
1380
|
return cached;
|
|
1152
1381
|
}
|
|
1153
1382
|
try {
|
|
@@ -1302,7 +1531,9 @@ function formatMarkdown(repos) {
|
|
|
1302
1531
|
return [header, sep, ...rows].join("\n");
|
|
1303
1532
|
}
|
|
1304
1533
|
function formatText(repos) {
|
|
1305
|
-
return repos.map(
|
|
1534
|
+
return repos.map(
|
|
1535
|
+
(r, i) => `${i + 1}. ${r.fullName} \u2605 ${r.stars.toLocaleString()} ${r.url}`
|
|
1536
|
+
).join("\n");
|
|
1306
1537
|
}
|
|
1307
1538
|
function formatUrls(repos) {
|
|
1308
1539
|
return repos.map((r) => r.url).join("\n");
|
|
@@ -1359,12 +1590,21 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1359
1590
|
const trendingSearch = createTrendingSearch();
|
|
1360
1591
|
const response = await trendingSearch.search(
|
|
1361
1592
|
{ keywords: [], qualifiers: [], raw: "trending" },
|
|
1362
|
-
{
|
|
1593
|
+
{
|
|
1594
|
+
limit: opts.limit,
|
|
1595
|
+
sort: opts.sort,
|
|
1596
|
+
json: false,
|
|
1597
|
+
verbose: false,
|
|
1598
|
+
trendingSince: since
|
|
1599
|
+
}
|
|
1363
1600
|
);
|
|
1364
1601
|
repos = response.repos;
|
|
1365
1602
|
} else {
|
|
1366
1603
|
const parsed = applyFlagFilters(parseQuery(opts.query), {});
|
|
1367
|
-
const provider = createGitHubSearch(
|
|
1604
|
+
const provider = createGitHubSearch(
|
|
1605
|
+
void 0,
|
|
1606
|
+
opts.token ? [opts.token] : []
|
|
1607
|
+
);
|
|
1368
1608
|
const options = {
|
|
1369
1609
|
limit: opts.limit,
|
|
1370
1610
|
sort: opts.sort,
|
|
@@ -1383,7 +1623,9 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1383
1623
|
failCount++;
|
|
1384
1624
|
onError(err instanceof Error ? err : new Error(String(err)));
|
|
1385
1625
|
if (failCount >= MAX_FAILS) {
|
|
1386
|
-
onError(
|
|
1626
|
+
onError(
|
|
1627
|
+
new Error(`Watch stopped after ${MAX_FAILS} consecutive failures`)
|
|
1628
|
+
);
|
|
1387
1629
|
if (intervalId) clearInterval(intervalId);
|
|
1388
1630
|
}
|
|
1389
1631
|
}
|
|
@@ -1450,9 +1692,12 @@ async function fetchOrgProfile(org, options = {}) {
|
|
|
1450
1692
|
const limit = Math.min(Math.max(options.limit ?? 100, 1), 500);
|
|
1451
1693
|
let orgPayload;
|
|
1452
1694
|
try {
|
|
1453
|
-
const res = await fetch(
|
|
1454
|
-
|
|
1455
|
-
|
|
1695
|
+
const res = await fetch(
|
|
1696
|
+
`https://api.github.com/orgs/${encodeURIComponent(name)}`,
|
|
1697
|
+
{
|
|
1698
|
+
headers: apiHeaders(token)
|
|
1699
|
+
}
|
|
1700
|
+
);
|
|
1456
1701
|
if (res.status === 404) {
|
|
1457
1702
|
throw new Error(`Organization "${name}" not found.`);
|
|
1458
1703
|
}
|
|
@@ -1462,8 +1707,11 @@ async function fetchOrgProfile(org, options = {}) {
|
|
|
1462
1707
|
}
|
|
1463
1708
|
orgPayload = await res.json();
|
|
1464
1709
|
} catch (err) {
|
|
1465
|
-
if (err instanceof Error && err.message.includes(`Organization "${name}"`))
|
|
1466
|
-
|
|
1710
|
+
if (err instanceof Error && err.message.includes(`Organization "${name}"`))
|
|
1711
|
+
throw err;
|
|
1712
|
+
throw new Error(
|
|
1713
|
+
`Failed to fetch org "${name}": ${err instanceof Error ? err.message : String(err)}`
|
|
1714
|
+
);
|
|
1467
1715
|
}
|
|
1468
1716
|
const repos = [];
|
|
1469
1717
|
if (orgPayload.public_repos > 0) {
|
|
@@ -1493,9 +1741,15 @@ function formatOrgText(p) {
|
|
|
1493
1741
|
if (p.blog) meta.push(`Web ${p.blog}`);
|
|
1494
1742
|
if (p.createdAt) meta.push(`Created ${p.createdAt.slice(0, 10)}`);
|
|
1495
1743
|
if (meta.length > 0) lines.push(meta.join("\n"));
|
|
1496
|
-
lines.push(
|
|
1497
|
-
|
|
1498
|
-
|
|
1744
|
+
lines.push(
|
|
1745
|
+
`Repos ${p.publicRepoCount.toLocaleString()} public${p.fetchedRepoCount < p.publicRepoCount ? ` (aggregated ${p.fetchedRepoCount})` : ""}`
|
|
1746
|
+
);
|
|
1747
|
+
lines.push(
|
|
1748
|
+
`Stars ${p.totalStars.toLocaleString()} (sum of aggregated repos)`
|
|
1749
|
+
);
|
|
1750
|
+
lines.push(
|
|
1751
|
+
`Forks ${p.totalForks.toLocaleString()} (sum of aggregated repos)`
|
|
1752
|
+
);
|
|
1499
1753
|
lines.push(`URL ${p.url}`);
|
|
1500
1754
|
if (p.topLanguages.length > 0) {
|
|
1501
1755
|
const langs = p.topLanguages.map((l) => `${l.language} (${l.repos})`).join(", ");
|
|
@@ -1505,7 +1759,9 @@ function formatOrgText(p) {
|
|
|
1505
1759
|
lines.push("");
|
|
1506
1760
|
lines.push("Top repos by stars:");
|
|
1507
1761
|
for (const r of p.topRepos) {
|
|
1508
|
-
lines.push(
|
|
1762
|
+
lines.push(
|
|
1763
|
+
` ${r.fullName} \u2605 ${r.stars.toLocaleString()} ${r.language ?? ""}`
|
|
1764
|
+
);
|
|
1509
1765
|
}
|
|
1510
1766
|
}
|
|
1511
1767
|
if (p.activeRepos.length > 0) {
|
|
@@ -1532,7 +1788,10 @@ function formatOrgCsv(p) {
|
|
|
1532
1788
|
p.topLanguages.map((l) => l.language).join(";"),
|
|
1533
1789
|
p.createdAt
|
|
1534
1790
|
].map(esc);
|
|
1535
|
-
return [
|
|
1791
|
+
return [
|
|
1792
|
+
"login,name,url,public_repos,total_stars,total_forks,top_languages,created_at",
|
|
1793
|
+
row.join(",")
|
|
1794
|
+
].join("\n");
|
|
1536
1795
|
}
|
|
1537
1796
|
function formatOrgMarkdown(p) {
|
|
1538
1797
|
const lines = [];
|
|
@@ -1547,19 +1806,36 @@ function formatOrgMarkdown(p) {
|
|
|
1547
1806
|
`| Total forks (aggregated) | ${p.totalForks.toLocaleString()} |`
|
|
1548
1807
|
);
|
|
1549
1808
|
if (p.topLanguages.length > 0) {
|
|
1550
|
-
lines.push(
|
|
1809
|
+
lines.push(
|
|
1810
|
+
`| Top languages | ${p.topLanguages.map((l) => `${l.language} (${l.repos})`).join(", ")} |`
|
|
1811
|
+
);
|
|
1551
1812
|
}
|
|
1552
1813
|
if (p.topRepos.length > 0) {
|
|
1553
|
-
lines.push(
|
|
1814
|
+
lines.push(
|
|
1815
|
+
"",
|
|
1816
|
+
"## Top repos by stars",
|
|
1817
|
+
"",
|
|
1818
|
+
"| Repo | Stars | Language |",
|
|
1819
|
+
"| --- | --- | --- |"
|
|
1820
|
+
);
|
|
1554
1821
|
for (const r of p.topRepos) {
|
|
1555
|
-
lines.push(
|
|
1822
|
+
lines.push(
|
|
1823
|
+
`| [${r.fullName}](${r.url}) | ${r.stars.toLocaleString()} | ${r.language ?? "\u2014"} |`
|
|
1824
|
+
);
|
|
1556
1825
|
}
|
|
1557
1826
|
}
|
|
1558
1827
|
return lines.join("\n");
|
|
1559
1828
|
}
|
|
1560
1829
|
|
|
1561
1830
|
// src/storage.ts
|
|
1562
|
-
import {
|
|
1831
|
+
import {
|
|
1832
|
+
existsSync as existsSync2,
|
|
1833
|
+
mkdirSync as mkdirSync2,
|
|
1834
|
+
readFileSync as readFileSync3,
|
|
1835
|
+
writeFileSync as writeFileSync2,
|
|
1836
|
+
appendFileSync,
|
|
1837
|
+
unlinkSync
|
|
1838
|
+
} from "fs";
|
|
1563
1839
|
import { join as join3 } from "path";
|
|
1564
1840
|
function ensureDir() {
|
|
1565
1841
|
mkdirSync2(stateDir(), { recursive: true });
|
|
@@ -1579,7 +1855,11 @@ function writeJSON(filename, data) {
|
|
|
1579
1855
|
function appendJSONL(filename, obj) {
|
|
1580
1856
|
ensureDir();
|
|
1581
1857
|
try {
|
|
1582
|
-
appendFileSync(
|
|
1858
|
+
appendFileSync(
|
|
1859
|
+
join3(stateDir(), filename),
|
|
1860
|
+
JSON.stringify(obj) + "\n",
|
|
1861
|
+
"utf-8"
|
|
1862
|
+
);
|
|
1583
1863
|
} catch {
|
|
1584
1864
|
}
|
|
1585
1865
|
}
|
|
@@ -1600,8 +1880,12 @@ function writeRaw(filename, data) {
|
|
|
1600
1880
|
function debugLog(msg) {
|
|
1601
1881
|
if (!process.env.DEBUG) return;
|
|
1602
1882
|
try {
|
|
1603
|
-
appendFileSync(
|
|
1604
|
-
|
|
1883
|
+
appendFileSync(
|
|
1884
|
+
join3(stateDir(), "ghfind-debug.log"),
|
|
1885
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
1886
|
+
`,
|
|
1887
|
+
"utf-8"
|
|
1888
|
+
);
|
|
1605
1889
|
} catch {
|
|
1606
1890
|
}
|
|
1607
1891
|
}
|
|
@@ -1659,7 +1943,13 @@ function save() {
|
|
|
1659
1943
|
}
|
|
1660
1944
|
function addNotification(type, message) {
|
|
1661
1945
|
if (notifs.length === 0) load();
|
|
1662
|
-
const n = {
|
|
1946
|
+
const n = {
|
|
1947
|
+
id: nextId++,
|
|
1948
|
+
type,
|
|
1949
|
+
icon: iconFor(type),
|
|
1950
|
+
message,
|
|
1951
|
+
timestamp: Date.now()
|
|
1952
|
+
};
|
|
1663
1953
|
notifs.unshift(n);
|
|
1664
1954
|
if (notifs.length > MAX_NOTIFS) notifs = notifs.slice(0, MAX_NOTIFS);
|
|
1665
1955
|
save();
|
|
@@ -1714,10 +2004,15 @@ function toTracked(raw) {
|
|
|
1714
2004
|
}));
|
|
1715
2005
|
}
|
|
1716
2006
|
async function fetchReleases(fullName, etag, token) {
|
|
1717
|
-
const headers = {
|
|
2007
|
+
const headers = {
|
|
2008
|
+
Accept: "application/vnd.github+json"
|
|
2009
|
+
};
|
|
1718
2010
|
if (etag) headers["If-None-Match"] = etag;
|
|
1719
2011
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1720
|
-
const res = await fetch(
|
|
2012
|
+
const res = await fetch(
|
|
2013
|
+
`https://api.github.com/repos/${fullName}/releases?per_page=${PER_REPO}`,
|
|
2014
|
+
{ headers }
|
|
2015
|
+
);
|
|
1721
2016
|
if (res.status === 304 || !res.ok) return { status: res.status };
|
|
1722
2017
|
return {
|
|
1723
2018
|
status: 200,
|
|
@@ -1736,19 +2031,37 @@ async function checkReleases(opts = {}) {
|
|
|
1736
2031
|
const prev = cache[bm.repo.fullName];
|
|
1737
2032
|
const lastSeen = getLastSeen(bm.repo.fullName);
|
|
1738
2033
|
const isNew = (r) => !r.publishedAt || new Date(r.publishedAt).getTime() > lastSeen;
|
|
1739
|
-
const { status, etag, releases } = await fetchReleases(
|
|
2034
|
+
const { status, etag, releases } = await fetchReleases(
|
|
2035
|
+
bm.repo.fullName,
|
|
2036
|
+
prev?.etag,
|
|
2037
|
+
token
|
|
2038
|
+
);
|
|
1740
2039
|
if (status === 304 && prev) {
|
|
1741
|
-
results2.push({
|
|
2040
|
+
results2.push({
|
|
2041
|
+
fullName: bm.repo.fullName,
|
|
2042
|
+
newReleases: prev.releases.filter(isNew)
|
|
2043
|
+
});
|
|
1742
2044
|
continue;
|
|
1743
2045
|
}
|
|
1744
2046
|
if (status !== 200 || !releases) {
|
|
1745
|
-
results2.push({
|
|
2047
|
+
results2.push({
|
|
2048
|
+
fullName: bm.repo.fullName,
|
|
2049
|
+
newReleases: [],
|
|
2050
|
+
error: `HTTP ${status}`
|
|
2051
|
+
});
|
|
1746
2052
|
continue;
|
|
1747
2053
|
}
|
|
1748
2054
|
cache[bm.repo.fullName] = { checkedAt: Date.now(), etag, releases };
|
|
1749
|
-
results2.push({
|
|
2055
|
+
results2.push({
|
|
2056
|
+
fullName: bm.repo.fullName,
|
|
2057
|
+
newReleases: releases.filter(isNew)
|
|
2058
|
+
});
|
|
1750
2059
|
} catch (err) {
|
|
1751
|
-
results2.push({
|
|
2060
|
+
results2.push({
|
|
2061
|
+
fullName: bm.repo.fullName,
|
|
2062
|
+
newReleases: [],
|
|
2063
|
+
error: err.message
|
|
2064
|
+
});
|
|
1752
2065
|
}
|
|
1753
2066
|
}
|
|
1754
2067
|
saveCache(cache);
|
|
@@ -1770,81 +2083,142 @@ async function checkAndNotify(opts = {}) {
|
|
|
1770
2083
|
function allCachedReleases() {
|
|
1771
2084
|
const cache = loadCache();
|
|
1772
2085
|
const bookmarks = getBookmarks();
|
|
1773
|
-
return Object.entries(cache).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
|
|
2086
|
+
return Object.entries(cache).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
|
|
2087
|
+
(a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
|
|
2088
|
+
);
|
|
1774
2089
|
}
|
|
1775
2090
|
|
|
1776
2091
|
// src/init.ts
|
|
1777
|
-
import { createInterface } from "readline";
|
|
2092
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1778
2093
|
|
|
1779
|
-
// src/
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
borderAccent: "#1f2335"
|
|
1808
|
-
},
|
|
1809
|
-
"premium-dark": {
|
|
1810
|
-
name: "premium-dark",
|
|
1811
|
-
// Warm counter-mood to Tokyo Night — amber/rust accent
|
|
1812
|
-
bg: "#1e1e2e",
|
|
1813
|
-
surface: "#262637",
|
|
1814
|
-
surfaceAlt: "#2e2e42",
|
|
1815
|
-
text: "#cdd6f4",
|
|
1816
|
-
muted: "#6c7086",
|
|
1817
|
-
blue: "#89b4fa",
|
|
1818
|
-
green: "#a6e3a1",
|
|
1819
|
-
yellow: "#f9e2af",
|
|
1820
|
-
red: "#f38ba8",
|
|
1821
|
-
teal: "#94e2d5",
|
|
1822
|
-
purple: "#cba6f7",
|
|
1823
|
-
orange: "#fab387",
|
|
1824
|
-
border: "#313244",
|
|
1825
|
-
borderAlt: "#2a2a3d",
|
|
1826
|
-
separator: "#2a2a3d",
|
|
1827
|
-
selectionBg: "#45475a",
|
|
1828
|
-
selectionText: "#cdd6f4",
|
|
1829
|
-
success: "#a6e3a1",
|
|
1830
|
-
warning: "#f9e2af",
|
|
1831
|
-
error: "#f38ba8",
|
|
1832
|
-
surfaceDim: "#181825",
|
|
1833
|
-
accent: "#f9e2af",
|
|
1834
|
-
accentDim: "#c9a87a",
|
|
1835
|
-
borderAccent: "#313244"
|
|
2094
|
+
// src/login.ts
|
|
2095
|
+
import { createInterface } from "readline";
|
|
2096
|
+
import { homedir as homedir2 } from "os";
|
|
2097
|
+
import { join as join4 } from "path";
|
|
2098
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2099
|
+
var TOKEN_PREFIXES = ["ghp_", "gho_", "github_pat_"];
|
|
2100
|
+
function isLikelyGithubToken(token) {
|
|
2101
|
+
return TOKEN_PREFIXES.some((prefix) => token.startsWith(prefix));
|
|
2102
|
+
}
|
|
2103
|
+
function maskToken(token) {
|
|
2104
|
+
if (token.length <= 11) return `${token.slice(0, 4)}\u2026`;
|
|
2105
|
+
return `${token.slice(0, 7)}\u2026${token.slice(-4)}`;
|
|
2106
|
+
}
|
|
2107
|
+
function ghCliConfigDir() {
|
|
2108
|
+
return process.env.GH_CONFIG_DIR || join4(homedir2(), ".config", "gh");
|
|
2109
|
+
}
|
|
2110
|
+
function parseHostsYmlToken(text, host = "github.com") {
|
|
2111
|
+
let inHost = false;
|
|
2112
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2113
|
+
const trimmed = line.trim();
|
|
2114
|
+
if (/^[A-Za-z0-9.-]+:\s*$/.test(trimmed)) {
|
|
2115
|
+
inHost = trimmed.slice(0, -1).trim() === host;
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
if (inHost) {
|
|
2119
|
+
const match = trimmed.match(/^oauth_token:\s*["']?([^"'\s]+)/);
|
|
2120
|
+
if (match) return match[1];
|
|
2121
|
+
}
|
|
1836
2122
|
}
|
|
1837
|
-
|
|
1838
|
-
function loadTheme(name) {
|
|
1839
|
-
return THEMES[name] ?? THEMES["tokyo-night"];
|
|
2123
|
+
return void 0;
|
|
1840
2124
|
}
|
|
1841
|
-
function
|
|
1842
|
-
|
|
2125
|
+
function readGhCliToken(ghDir = ghCliConfigDir(), host = process.env.GH_HOST ?? "github.com") {
|
|
2126
|
+
const hostsFile = join4(ghDir, "hosts.yml");
|
|
2127
|
+
if (!existsSync3(hostsFile)) return void 0;
|
|
2128
|
+
try {
|
|
2129
|
+
return parseHostsYmlToken(readFileSync4(hostsFile, "utf-8"), host);
|
|
2130
|
+
} catch {
|
|
2131
|
+
return void 0;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
function persistToken(token, deps) {
|
|
2135
|
+
const config = deps.readConfig();
|
|
2136
|
+
config.githubToken = token;
|
|
2137
|
+
deps.writeConfig(config);
|
|
2138
|
+
}
|
|
2139
|
+
function createAsk() {
|
|
2140
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2141
|
+
return (query) => new Promise((resolve) => {
|
|
2142
|
+
rl.question(query, (answer) => {
|
|
2143
|
+
rl.close();
|
|
2144
|
+
resolve(answer.trim());
|
|
2145
|
+
});
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function defaultLoginDeps() {
|
|
2149
|
+
return {
|
|
2150
|
+
ask: createAsk(),
|
|
2151
|
+
readGhCliToken: () => readGhCliToken(),
|
|
2152
|
+
readConfig: loadConfig,
|
|
2153
|
+
writeConfig: (config) => saveConfig(config)
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
function successNote() {
|
|
2157
|
+
return [
|
|
2158
|
+
" Authentication raises the search rate limit from 60 to",
|
|
2159
|
+
" 5,000 requests/hour. Remove the token anytime by editing",
|
|
2160
|
+
` ${configPath()} \u2014 delete the "githubToken" line and save.`
|
|
2161
|
+
];
|
|
2162
|
+
}
|
|
2163
|
+
async function runLoginWizard(deps = defaultLoginDeps()) {
|
|
2164
|
+
const { ask: ask2 } = deps;
|
|
2165
|
+
console.log("");
|
|
2166
|
+
console.log(" ghfind login");
|
|
2167
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2168
|
+
console.log("");
|
|
2169
|
+
if (process.env.GITHUB_TOKEN) {
|
|
2170
|
+
console.log(
|
|
2171
|
+
" \u2139 GITHUB_TOKEN is set in the environment and already takes precedence."
|
|
2172
|
+
);
|
|
2173
|
+
console.log("");
|
|
2174
|
+
}
|
|
2175
|
+
const current = deps.readConfig();
|
|
2176
|
+
if (current.githubToken) {
|
|
2177
|
+
console.log(
|
|
2178
|
+
` \u2713 A token is already saved: ${maskToken(current.githubToken)}`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
const ghToken = deps.readGhCliToken();
|
|
2182
|
+
if (ghToken) {
|
|
2183
|
+
console.log(" \u2713 Found a token managed by the GitHub CLI (gh).");
|
|
2184
|
+
const answer = await ask2(" Import it into ghfind? [Y/n]: ");
|
|
2185
|
+
if (answer.toLowerCase() !== "n") {
|
|
2186
|
+
persistToken(ghToken, deps);
|
|
2187
|
+
finishLogin(ghToken);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
const pasted = await ask2(" Paste a GitHub token (Enter to skip): ");
|
|
2192
|
+
if (!pasted) {
|
|
2193
|
+
console.log(" Skipped \u2014 no token saved.");
|
|
2194
|
+
console.log("");
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
if (!isLikelyGithubToken(pasted)) {
|
|
2198
|
+
console.log(
|
|
2199
|
+
` \u26A0 That doesn't look like a GitHub token (expected one of: ${TOKEN_PREFIXES.join(", ")}).`
|
|
2200
|
+
);
|
|
2201
|
+
const confirm = await ask2(" Save it anyway? [y/N]: ");
|
|
2202
|
+
if (confirm.toLowerCase() !== "y") {
|
|
2203
|
+
console.log(" Cancelled \u2014 nothing saved.");
|
|
2204
|
+
console.log("");
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
persistToken(pasted, deps);
|
|
2209
|
+
finishLogin(pasted);
|
|
2210
|
+
}
|
|
2211
|
+
function finishLogin(token) {
|
|
2212
|
+
console.log(` \u2713 Token saved to ${configPath()}`);
|
|
2213
|
+
console.log(` (masked: ${maskToken(token)})`);
|
|
2214
|
+
console.log("");
|
|
2215
|
+
for (const line of successNote()) console.log(line);
|
|
2216
|
+
console.log("");
|
|
1843
2217
|
}
|
|
1844
2218
|
|
|
1845
2219
|
// src/init.ts
|
|
1846
2220
|
function ask(query) {
|
|
1847
|
-
const rl =
|
|
2221
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1848
2222
|
return new Promise((resolve) => {
|
|
1849
2223
|
rl.question(query, (answer) => {
|
|
1850
2224
|
rl.close();
|
|
@@ -1858,10 +2232,14 @@ async function runInitWizard() {
|
|
|
1858
2232
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1859
2233
|
console.log("");
|
|
1860
2234
|
const token = await ask(" GitHub token (optional, press Enter to skip): ");
|
|
1861
|
-
if (token && !
|
|
1862
|
-
console.log(
|
|
2235
|
+
if (token && !isLikelyGithubToken(token)) {
|
|
2236
|
+
console.log(
|
|
2237
|
+
" \u26A0 Token doesn't look like a GitHub token (should start with ghp_, gho_, or github_pat_)"
|
|
2238
|
+
);
|
|
1863
2239
|
}
|
|
1864
|
-
const sortStr = await ask(
|
|
2240
|
+
const sortStr = await ask(
|
|
2241
|
+
" Default sort [best-match/stars/updated/forks] (best-match): "
|
|
2242
|
+
);
|
|
1865
2243
|
const limitStr = await ask(" Default result limit [10-100] (50): ");
|
|
1866
2244
|
const themes = listThemes();
|
|
1867
2245
|
const themeStr = await ask(` Theme [${themes.join("/")}] (tokyo-night): `);
|
|
@@ -1874,7 +2252,7 @@ async function runInitWizard() {
|
|
|
1874
2252
|
};
|
|
1875
2253
|
if (token) config.githubToken = token;
|
|
1876
2254
|
saveConfig(config);
|
|
1877
|
-
const { configPath: configPath2 } = await import("./config-
|
|
2255
|
+
const { configPath: configPath2 } = await import("./config-OCIPF2IF.js");
|
|
1878
2256
|
console.log(` \u2713 Config saved to ${configPath2()}`);
|
|
1879
2257
|
console.log(" Run 'ghfind' to start browsing!");
|
|
1880
2258
|
console.log("");
|
|
@@ -1882,16 +2260,35 @@ async function runInitWizard() {
|
|
|
1882
2260
|
|
|
1883
2261
|
// src/compare.ts
|
|
1884
2262
|
function buildComparisonTable(repos) {
|
|
1885
|
-
if (repos.length < 2)
|
|
2263
|
+
if (repos.length < 2)
|
|
2264
|
+
return "Select at least 2 repos to compare (press c to add).";
|
|
1886
2265
|
const rows = [];
|
|
1887
2266
|
const names = repos.map((r) => r.fullName);
|
|
1888
|
-
rows.push({
|
|
1889
|
-
|
|
2267
|
+
rows.push({
|
|
2268
|
+
label: "Stars",
|
|
2269
|
+
values: repos.map((r) => `\u2605 ${r.stars.toLocaleString()}`)
|
|
2270
|
+
});
|
|
2271
|
+
rows.push({
|
|
2272
|
+
label: "Forks",
|
|
2273
|
+
values: repos.map((r) => r.forks.toLocaleString())
|
|
2274
|
+
});
|
|
1890
2275
|
rows.push({ label: "Language", values: repos.map((r) => r.language ?? "\u2014") });
|
|
1891
|
-
rows.push({
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2276
|
+
rows.push({
|
|
2277
|
+
label: "Created",
|
|
2278
|
+
values: repos.map((r) => r.createdAt?.slice(0, 7) ?? "\u2014")
|
|
2279
|
+
});
|
|
2280
|
+
rows.push({
|
|
2281
|
+
label: "Updated",
|
|
2282
|
+
values: repos.map((r) => r.updatedAt?.slice(0, 10) ?? "\u2014")
|
|
2283
|
+
});
|
|
2284
|
+
rows.push({
|
|
2285
|
+
label: "Topics",
|
|
2286
|
+
values: repos.map((r) => (r.topics?.slice(0, 5).join(", ") ?? "\u2014") || "\u2014")
|
|
2287
|
+
});
|
|
2288
|
+
rows.push({
|
|
2289
|
+
label: "Description",
|
|
2290
|
+
values: repos.map((r) => r.description?.slice(0, 50) ?? "\u2014")
|
|
2291
|
+
});
|
|
1895
2292
|
rows.push({ label: "URL", values: repos.map((r) => r.url || "\u2014") });
|
|
1896
2293
|
const colW = Math.max(35, ...names.map((n) => n.length));
|
|
1897
2294
|
const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
|
|
@@ -1902,7 +2299,10 @@ function buildComparisonTable(repos) {
|
|
|
1902
2299
|
"\u251C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u253C") + "\u2524"
|
|
1903
2300
|
];
|
|
1904
2301
|
for (const row of rows) {
|
|
1905
|
-
const cells = [
|
|
2302
|
+
const cells = [
|
|
2303
|
+
row.label.padEnd(14),
|
|
2304
|
+
...row.values.map((v) => v.padEnd(colW))
|
|
2305
|
+
];
|
|
1906
2306
|
lines.push("\u2502" + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
|
|
1907
2307
|
}
|
|
1908
2308
|
lines.push("\u2514" + sep.map((s) => "\u2500" + s + "\u2500").join("\u2534") + "\u2518");
|
|
@@ -1910,8 +2310,8 @@ function buildComparisonTable(repos) {
|
|
|
1910
2310
|
}
|
|
1911
2311
|
|
|
1912
2312
|
// src/cli.ts
|
|
1913
|
-
import { readFileSync as
|
|
1914
|
-
import { join as
|
|
2313
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2314
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
1915
2315
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1916
2316
|
|
|
1917
2317
|
// src/tui.ts
|
|
@@ -1948,7 +2348,8 @@ function tabSince(tab) {
|
|
|
1948
2348
|
}
|
|
1949
2349
|
}
|
|
1950
2350
|
function fmtStars(n) {
|
|
1951
|
-
if (n >= 1e6)
|
|
2351
|
+
if (n >= 1e6)
|
|
2352
|
+
return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
|
|
1952
2353
|
if (n >= 999500) return "1M";
|
|
1953
2354
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
|
|
1954
2355
|
return String(n);
|
|
@@ -2125,8 +2526,13 @@ function deleteHistoryEntry(index) {
|
|
|
2125
2526
|
const entries = readHistory();
|
|
2126
2527
|
if (index < 0 || index >= entries.length) return;
|
|
2127
2528
|
const removed = entries[index];
|
|
2128
|
-
const remaining = entries.filter(
|
|
2129
|
-
|
|
2529
|
+
const remaining = entries.filter(
|
|
2530
|
+
(e) => e.timestamp !== removed.timestamp || e.query !== removed.query
|
|
2531
|
+
);
|
|
2532
|
+
writeRaw(
|
|
2533
|
+
HISTORY_FILE,
|
|
2534
|
+
remaining.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
2535
|
+
);
|
|
2130
2536
|
}
|
|
2131
2537
|
function clearHistory() {
|
|
2132
2538
|
writeRaw(HISTORY_FILE, "");
|
|
@@ -2135,7 +2541,10 @@ function rotateHistory() {
|
|
|
2135
2541
|
const entries = readHistory();
|
|
2136
2542
|
if (entries.length <= MAX_ENTRIES) return;
|
|
2137
2543
|
const trimmed = entries.slice(0, MAX_ENTRIES);
|
|
2138
|
-
writeRaw(
|
|
2544
|
+
writeRaw(
|
|
2545
|
+
HISTORY_FILE,
|
|
2546
|
+
trimmed.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
2547
|
+
);
|
|
2139
2548
|
}
|
|
2140
2549
|
|
|
2141
2550
|
// src/saved-searches.ts
|
|
@@ -2147,15 +2556,30 @@ function saveRaw2(searches) {
|
|
|
2147
2556
|
writeJSON(SAVED_FILE, searches);
|
|
2148
2557
|
}
|
|
2149
2558
|
function getSavedSearches() {
|
|
2150
|
-
return loadRaw2().sort(
|
|
2559
|
+
return loadRaw2().sort(
|
|
2560
|
+
(a, b) => (b.lastRunAt ?? b.createdAt) - (a.lastRunAt ?? a.createdAt)
|
|
2561
|
+
);
|
|
2151
2562
|
}
|
|
2152
2563
|
function saveSearch(name, query, mode, sort, limit, tab) {
|
|
2153
2564
|
const searches = loadRaw2();
|
|
2154
2565
|
const now = Date.now();
|
|
2155
2566
|
const existing = searches.findIndex((s) => s.name === name);
|
|
2156
|
-
const entry = {
|
|
2567
|
+
const entry = {
|
|
2568
|
+
name,
|
|
2569
|
+
query,
|
|
2570
|
+
mode,
|
|
2571
|
+
sort,
|
|
2572
|
+
limit,
|
|
2573
|
+
tab,
|
|
2574
|
+
createdAt: now,
|
|
2575
|
+
lastRunAt: now
|
|
2576
|
+
};
|
|
2157
2577
|
if (existing >= 0) {
|
|
2158
|
-
searches[existing] = {
|
|
2578
|
+
searches[existing] = {
|
|
2579
|
+
...searches[existing],
|
|
2580
|
+
...entry,
|
|
2581
|
+
createdAt: searches[existing].createdAt
|
|
2582
|
+
};
|
|
2159
2583
|
} else {
|
|
2160
2584
|
searches.push(entry);
|
|
2161
2585
|
}
|
|
@@ -2181,7 +2605,9 @@ function saveSession(state) {
|
|
|
2181
2605
|
writeJSON(SESSION_FILE, state);
|
|
2182
2606
|
} catch (err) {
|
|
2183
2607
|
if (process.env.DEBUG)
|
|
2184
|
-
console.error(
|
|
2608
|
+
console.error(
|
|
2609
|
+
`[ghfind] Failed to save session: ${err instanceof Error ? err.message : String(err)}`
|
|
2610
|
+
);
|
|
2185
2611
|
}
|
|
2186
2612
|
}
|
|
2187
2613
|
function restoreSession() {
|
|
@@ -2190,16 +2616,31 @@ function restoreSession() {
|
|
|
2190
2616
|
|
|
2191
2617
|
// src/deepdive.ts
|
|
2192
2618
|
var USER_AGENT = "ghfind/1.0";
|
|
2619
|
+
function buildHeaders(token) {
|
|
2620
|
+
const headers = { "User-Agent": USER_AGENT };
|
|
2621
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2622
|
+
return headers;
|
|
2623
|
+
}
|
|
2193
2624
|
async function fetchDeepDive(repo, token) {
|
|
2194
|
-
const
|
|
2195
|
-
const headers = { "User-Agent": USER_AGENT, ...auth };
|
|
2625
|
+
const headers = buildHeaders(token);
|
|
2196
2626
|
const [languagesRes, contributorsRes] = await Promise.all([
|
|
2197
|
-
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/languages`, {
|
|
2198
|
-
|
|
2627
|
+
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/languages`, {
|
|
2628
|
+
headers
|
|
2629
|
+
}),
|
|
2630
|
+
fetch(
|
|
2631
|
+
`https://api.github.com/repos/${repo.owner}/${repo.name}/contributors?per_page=5`,
|
|
2632
|
+
{ headers }
|
|
2633
|
+
)
|
|
2199
2634
|
]);
|
|
2200
2635
|
let readmeText = "";
|
|
2201
|
-
for (const path of [
|
|
2202
|
-
|
|
2636
|
+
for (const path of [
|
|
2637
|
+
`${repo.owner}/${repo.name}/main/README.md`,
|
|
2638
|
+
`${repo.owner}/${repo.name}/master/README.md`,
|
|
2639
|
+
`${repo.owner}/${repo.name}/main/README.rst`
|
|
2640
|
+
]) {
|
|
2641
|
+
const r = await fetch(`https://raw.githubusercontent.com/${path}`, {
|
|
2642
|
+
headers
|
|
2643
|
+
});
|
|
2203
2644
|
if (r.ok) {
|
|
2204
2645
|
readmeText = await r.text();
|
|
2205
2646
|
break;
|
|
@@ -2207,8 +2648,12 @@ async function fetchDeepDive(repo, token) {
|
|
|
2207
2648
|
}
|
|
2208
2649
|
return {
|
|
2209
2650
|
summary: formatSummary(repo),
|
|
2210
|
-
languages: formatLanguages(
|
|
2211
|
-
|
|
2651
|
+
languages: formatLanguages(
|
|
2652
|
+
languagesRes.ok ? await languagesRes.json().catch(() => ({})) : {}
|
|
2653
|
+
),
|
|
2654
|
+
contributors: formatContributors(
|
|
2655
|
+
contributorsRes.ok ? await contributorsRes.json().catch(() => []) : []
|
|
2656
|
+
),
|
|
2212
2657
|
readme: formatReadme(readmeText)
|
|
2213
2658
|
};
|
|
2214
2659
|
}
|
|
@@ -2242,7 +2687,8 @@ function formatReadme(readme) {
|
|
|
2242
2687
|
if (!readme) return " (no README)";
|
|
2243
2688
|
const stripped = readme.replace(/```[\s\S]*?```/g, "[code block]").replace(/#{1,6}\s/g, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1").replace(/`([^`]+)`/g, "$1").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2244
2689
|
const lines = stripped.slice(0, 30);
|
|
2245
|
-
if (stripped.length > 30)
|
|
2690
|
+
if (stripped.length > 30)
|
|
2691
|
+
lines.push("... (more lines, open in browser to read full)");
|
|
2246
2692
|
return lines.map((l) => ` ${l}`).join("\n");
|
|
2247
2693
|
}
|
|
2248
2694
|
function buildDeepDiveText(data) {
|
|
@@ -2297,12 +2743,7 @@ function renderMarkdown(md, opts = {}) {
|
|
|
2297
2743
|
const colCount = Math.max(...dataRows.map((r) => r.length));
|
|
2298
2744
|
const colW = [];
|
|
2299
2745
|
for (let ci = 0; ci < colCount; ci++) {
|
|
2300
|
-
colW.push(
|
|
2301
|
-
Math.max(
|
|
2302
|
-
3,
|
|
2303
|
-
...dataRows.map((r) => (r[ci] ?? "").length)
|
|
2304
|
-
)
|
|
2305
|
-
);
|
|
2746
|
+
colW.push(Math.max(3, ...dataRows.map((r) => (r[ci] ?? "").length)));
|
|
2306
2747
|
}
|
|
2307
2748
|
const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
|
|
2308
2749
|
if (totalW > width) {
|
|
@@ -2434,12 +2875,15 @@ function renderMarkdown(md, opts = {}) {
|
|
|
2434
2875
|
|
|
2435
2876
|
// src/explore.ts
|
|
2436
2877
|
async function fetchTopics() {
|
|
2437
|
-
const res = await fetch(
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2878
|
+
const res = await fetch(
|
|
2879
|
+
"https://api.github.com/search/topics?q=popular&per_page=30",
|
|
2880
|
+
{
|
|
2881
|
+
headers: {
|
|
2882
|
+
Accept: "application/vnd.github.mercy-preview+json",
|
|
2883
|
+
"User-Agent": "search-cli"
|
|
2884
|
+
}
|
|
2441
2885
|
}
|
|
2442
|
-
|
|
2886
|
+
);
|
|
2443
2887
|
if (!res.ok) throw new Error(`Topics API: HTTP ${res.status}`);
|
|
2444
2888
|
const data = await res.json();
|
|
2445
2889
|
return (data.items ?? []).map((item) => ({
|
|
@@ -2466,7 +2910,9 @@ var StatusManager = class {
|
|
|
2466
2910
|
this.type = type;
|
|
2467
2911
|
this.message = message;
|
|
2468
2912
|
if (type === "searching" || type === "loading") {
|
|
2469
|
-
this.startSpinner(
|
|
2913
|
+
this.startSpinner(
|
|
2914
|
+
type === "searching" ? SEARCH_SPINNER : LOADING_SPINNER
|
|
2915
|
+
);
|
|
2470
2916
|
} else {
|
|
2471
2917
|
this.emit();
|
|
2472
2918
|
}
|
|
@@ -2512,9 +2958,11 @@ function formatShare(repo, format2) {
|
|
|
2512
2958
|
async function copyToClipboard(text) {
|
|
2513
2959
|
try {
|
|
2514
2960
|
const p = process.platform;
|
|
2515
|
-
const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await
|
|
2961
|
+
const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await findLinuxClipboard();
|
|
2516
2962
|
if (!cmd) return false;
|
|
2517
|
-
const proc = spawn2(cmd[0], cmd.slice(1), {
|
|
2963
|
+
const proc = spawn2(cmd[0], cmd.slice(1), {
|
|
2964
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2965
|
+
});
|
|
2518
2966
|
proc.stdin.write(text);
|
|
2519
2967
|
proc.stdin.end();
|
|
2520
2968
|
return new Promise((resolve) => {
|
|
@@ -2525,6 +2973,20 @@ async function copyToClipboard(text) {
|
|
|
2525
2973
|
return false;
|
|
2526
2974
|
}
|
|
2527
2975
|
}
|
|
2976
|
+
async function findLinuxClipboard() {
|
|
2977
|
+
const { execFileSync } = await import("child_process");
|
|
2978
|
+
try {
|
|
2979
|
+
execFileSync("xclip", ["-version"], { stdio: "ignore" });
|
|
2980
|
+
return ["xclip", "-selection", "clipboard"];
|
|
2981
|
+
} catch {
|
|
2982
|
+
try {
|
|
2983
|
+
execFileSync("wl-copy", ["--version"], { stdio: "ignore" });
|
|
2984
|
+
return ["wl-copy"];
|
|
2985
|
+
} catch {
|
|
2986
|
+
return null;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2528
2990
|
|
|
2529
2991
|
// src/tips.ts
|
|
2530
2992
|
var TipRotator = class {
|
|
@@ -2560,14 +3022,14 @@ function nextTip() {
|
|
|
2560
3022
|
}
|
|
2561
3023
|
|
|
2562
3024
|
// src/update-check.ts
|
|
2563
|
-
import { readFileSync as
|
|
2564
|
-
import { join as
|
|
3025
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3026
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
2565
3027
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2566
3028
|
var REGISTRY_URL = "https://registry.npmjs.org/github-search-cli/latest";
|
|
2567
3029
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2568
3030
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
2569
3031
|
var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
|
|
2570
|
-
var PKG_DIR2 =
|
|
3032
|
+
var PKG_DIR2 = join5(__dirname2, "..");
|
|
2571
3033
|
var STATE_FILE = "update-state.json";
|
|
2572
3034
|
var DEFAULTS = {
|
|
2573
3035
|
lastCheck: 0,
|
|
@@ -2683,14 +3145,14 @@ function recordPreUpdateState(currentVersion) {
|
|
|
2683
3145
|
}
|
|
2684
3146
|
function fetchReleaseNotes(version) {
|
|
2685
3147
|
try {
|
|
2686
|
-
const notesPath =
|
|
2687
|
-
const md =
|
|
3148
|
+
const notesPath = join5(PKG_DIR2, "release-notes", `${version}.md`);
|
|
3149
|
+
const md = readFileSync5(notesPath, "utf-8");
|
|
2688
3150
|
if (md.trim().length > 0) return md;
|
|
2689
3151
|
} catch {
|
|
2690
3152
|
}
|
|
2691
3153
|
try {
|
|
2692
|
-
const changelogPath =
|
|
2693
|
-
const changelog =
|
|
3154
|
+
const changelogPath = join5(PKG_DIR2, "CHANGELOG.md");
|
|
3155
|
+
const changelog = readFileSync5(changelogPath, "utf-8");
|
|
2694
3156
|
const header = "## [" + version + "]";
|
|
2695
3157
|
const start = changelog.indexOf(header);
|
|
2696
3158
|
if (start === -1) return null;
|
|
@@ -2747,6 +3209,7 @@ var SORT_MODES = [
|
|
|
2747
3209
|
{ key: "forks", label: "forks" }
|
|
2748
3210
|
];
|
|
2749
3211
|
async function launchBrowser(theme_override) {
|
|
3212
|
+
const terminalBg = await detectTerminalBackground();
|
|
2750
3213
|
let renderer;
|
|
2751
3214
|
try {
|
|
2752
3215
|
const isTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
@@ -2782,24 +3245,22 @@ async function launchBrowser(theme_override) {
|
|
|
2782
3245
|
}
|
|
2783
3246
|
const root = renderer.root;
|
|
2784
3247
|
root.flexDirection = "column";
|
|
2785
|
-
root.border = true;
|
|
2786
|
-
root.borderTop = true;
|
|
2787
|
-
root.borderBottom = true;
|
|
2788
|
-
root.borderLeft = true;
|
|
2789
|
-
root.borderRight = true;
|
|
2790
|
-
root.borderColor = colors.borderAccent;
|
|
2791
|
-
root.backgroundColor = colors.bg;
|
|
2792
3248
|
const config = loadConfig();
|
|
2793
3249
|
const githubToken = config.githubToken || process.env.GITHUB_TOKEN;
|
|
2794
3250
|
const theme = loadTheme(theme_override || config.theme);
|
|
2795
3251
|
Object.assign(colors, theme);
|
|
3252
|
+
if (terminalBg) {
|
|
3253
|
+
colors.bg = terminalBg;
|
|
3254
|
+
Object.assign(colors, deriveSurfaceLayers(terminalBg));
|
|
3255
|
+
}
|
|
3256
|
+
renderer.setBackgroundColor(colors.bg);
|
|
2796
3257
|
const session = restoreSession();
|
|
2797
3258
|
let currentRepos = [];
|
|
2798
3259
|
let currentSort = session?.sort ?? config.defaultSort;
|
|
2799
3260
|
let currentLimit = session?.limit ?? config.defaultLimit;
|
|
2800
3261
|
let currentQueryInput = session?.query ?? "";
|
|
2801
3262
|
let isLoading = false;
|
|
2802
|
-
let currentMode = session?.mode
|
|
3263
|
+
let currentMode = session?.mode === "trending" || session?.mode === "packages" ? session.mode : "search";
|
|
2803
3264
|
let packages = [];
|
|
2804
3265
|
let currentPackageSort = "best-match";
|
|
2805
3266
|
let packageResultsRaw = [];
|
|
@@ -2815,11 +3276,9 @@ async function launchBrowser(theme_override) {
|
|
|
2815
3276
|
let quitArmed = false;
|
|
2816
3277
|
const header = new TextRenderable3(renderer, {
|
|
2817
3278
|
content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
height: 1
|
|
2821
|
-
borderBottom: true,
|
|
2822
|
-
borderBottomColor: colors.border
|
|
3279
|
+
bg: colors.bg,
|
|
3280
|
+
fg: colors.muted,
|
|
3281
|
+
height: 1
|
|
2823
3282
|
});
|
|
2824
3283
|
const trendingTabBox = new BoxRenderable3(renderer, {
|
|
2825
3284
|
flexDirection: "row",
|
|
@@ -2838,8 +3297,8 @@ async function launchBrowser(theme_override) {
|
|
|
2838
3297
|
const label = ` ${name} `;
|
|
2839
3298
|
const tt = new TextRenderable3(renderer, {
|
|
2840
3299
|
content: label,
|
|
2841
|
-
|
|
2842
|
-
|
|
3300
|
+
fg: isActive ? colors.bg : colors.muted,
|
|
3301
|
+
bg: isActive ? colors.blue : colors.bg,
|
|
2843
3302
|
height: 1
|
|
2844
3303
|
});
|
|
2845
3304
|
trendingTabTexts.push(tt);
|
|
@@ -2847,8 +3306,8 @@ async function launchBrowser(theme_override) {
|
|
|
2847
3306
|
if (i < TAB_NAMES.length - 1) {
|
|
2848
3307
|
const sp = new TextRenderable3(renderer, {
|
|
2849
3308
|
content: " ",
|
|
2850
|
-
|
|
2851
|
-
|
|
3309
|
+
fg: colors.muted,
|
|
3310
|
+
bg: colors.bg,
|
|
2852
3311
|
height: 1
|
|
2853
3312
|
});
|
|
2854
3313
|
trendingTabTexts.push(sp);
|
|
@@ -2858,20 +3317,13 @@ async function launchBrowser(theme_override) {
|
|
|
2858
3317
|
}
|
|
2859
3318
|
root.add(header);
|
|
2860
3319
|
root.add(trendingTabBox);
|
|
2861
|
-
const gapBox = new BoxRenderable3(renderer, {
|
|
2862
|
-
height: 1,
|
|
2863
|
-
visible: true
|
|
2864
|
-
});
|
|
2865
|
-
root.add(gapBox);
|
|
2866
3320
|
const searchBox = new BoxRenderable3(renderer, {
|
|
2867
3321
|
visible: true,
|
|
2868
|
-
bordered: true,
|
|
2869
|
-
borderColor: colors.border,
|
|
2870
|
-
title: "",
|
|
2871
|
-
titleColor: colors.blue,
|
|
2872
3322
|
width: "100%",
|
|
2873
3323
|
flexDirection: "row",
|
|
2874
3324
|
marginTop: 1,
|
|
3325
|
+
paddingY: 1,
|
|
3326
|
+
paddingLeft: 1,
|
|
2875
3327
|
backgroundColor: colors.surface
|
|
2876
3328
|
});
|
|
2877
3329
|
const searchInput = new InputRenderable(renderer, {
|
|
@@ -2879,8 +3331,6 @@ async function launchBrowser(theme_override) {
|
|
|
2879
3331
|
value: "",
|
|
2880
3332
|
backgroundColor: colors.surface,
|
|
2881
3333
|
textColor: colors.text,
|
|
2882
|
-
borderColor: colors.blue,
|
|
2883
|
-
selectedBorderColor: colors.blue,
|
|
2884
3334
|
paddingX: 0,
|
|
2885
3335
|
flexGrow: 1
|
|
2886
3336
|
});
|
|
@@ -2890,12 +3340,8 @@ async function launchBrowser(theme_override) {
|
|
|
2890
3340
|
content: formatToolbar(currentSort, currentLimit, totalCount),
|
|
2891
3341
|
visible: true,
|
|
2892
3342
|
// hidden in trending mode
|
|
2893
|
-
|
|
2894
|
-
// Surface panel
|
|
2895
|
-
color: colors.muted,
|
|
3343
|
+
fg: colors.muted,
|
|
2896
3344
|
height: 1,
|
|
2897
|
-
borderBottom: true,
|
|
2898
|
-
borderBottomColor: colors.border,
|
|
2899
3345
|
paddingX: 1
|
|
2900
3346
|
});
|
|
2901
3347
|
root.add(toolbarText);
|
|
@@ -2907,9 +3353,8 @@ async function launchBrowser(theme_override) {
|
|
|
2907
3353
|
paddingX: 1
|
|
2908
3354
|
});
|
|
2909
3355
|
const resultsBox = new BoxRenderable3(renderer, {
|
|
2910
|
-
|
|
3356
|
+
border: true,
|
|
2911
3357
|
borderColor: colors.border,
|
|
2912
|
-
borderBottomColor: colors.border,
|
|
2913
3358
|
title: " Results ",
|
|
2914
3359
|
titleColor: colors.blue,
|
|
2915
3360
|
width: "50%",
|
|
@@ -2937,11 +3382,6 @@ async function launchBrowser(theme_override) {
|
|
|
2937
3382
|
});
|
|
2938
3383
|
resultsBox.add(resultsSelect);
|
|
2939
3384
|
const detailBox = new BoxRenderable3(renderer, {
|
|
2940
|
-
bordered: true,
|
|
2941
|
-
borderColor: colors.border,
|
|
2942
|
-
borderBottomColor: colors.border,
|
|
2943
|
-
title: " Details ",
|
|
2944
|
-
titleColor: colors.green,
|
|
2945
3385
|
width: "50%",
|
|
2946
3386
|
flexDirection: "column",
|
|
2947
3387
|
paddingX: 1,
|
|
@@ -2951,7 +3391,7 @@ async function launchBrowser(theme_override) {
|
|
|
2951
3391
|
});
|
|
2952
3392
|
const detailText = new TextRenderable3(renderer, {
|
|
2953
3393
|
content: "",
|
|
2954
|
-
|
|
3394
|
+
fg: colors.text,
|
|
2955
3395
|
wrapMode: "none"
|
|
2956
3396
|
});
|
|
2957
3397
|
detailBox.add(detailText);
|
|
@@ -2975,8 +3415,8 @@ async function launchBrowser(theme_override) {
|
|
|
2975
3415
|
"\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554",
|
|
2976
3416
|
" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
2977
3417
|
].join("\n"),
|
|
2978
|
-
|
|
2979
|
-
|
|
3418
|
+
fg: colors.accent,
|
|
3419
|
+
bg: colors.bg,
|
|
2980
3420
|
height: 6
|
|
2981
3421
|
});
|
|
2982
3422
|
landingBox.add(landingBanner);
|
|
@@ -3031,7 +3471,7 @@ async function launchBrowser(theme_override) {
|
|
|
3031
3471
|
const spacerTop = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
3032
3472
|
const titleLine = new TextRenderable3(renderer, {
|
|
3033
3473
|
content: opt.title,
|
|
3034
|
-
|
|
3474
|
+
fg: i === 0 ? colors.accent : colors.text,
|
|
3035
3475
|
height: 1
|
|
3036
3476
|
});
|
|
3037
3477
|
const spacerBot = new TextRenderable3(renderer, { content: "", height: 1 });
|
|
@@ -3041,8 +3481,8 @@ async function launchBrowser(theme_override) {
|
|
|
3041
3481
|
}
|
|
3042
3482
|
const landingHint = new TextRenderable3(renderer, {
|
|
3043
3483
|
content: " \u2191\u2193 Navigate \u21E9 Select [?]help [q]uit",
|
|
3044
|
-
|
|
3045
|
-
|
|
3484
|
+
fg: colors.muted,
|
|
3485
|
+
bg: colors.bg,
|
|
3046
3486
|
height: 1,
|
|
3047
3487
|
marginTop: 1
|
|
3048
3488
|
});
|
|
@@ -3061,10 +3501,10 @@ async function launchBrowser(theme_override) {
|
|
|
3061
3501
|
for (let i = 0; i < landingCards.length; i++) {
|
|
3062
3502
|
const isSel = i === landingSelected;
|
|
3063
3503
|
landingCards[i].borderColor = isSel ? colors.accent : colors.border;
|
|
3064
|
-
const children = landingCards[i].
|
|
3504
|
+
const children = landingCards[i].getChildren();
|
|
3065
3505
|
const titleChild = children[1];
|
|
3066
3506
|
if (titleChild) {
|
|
3067
|
-
titleChild.
|
|
3507
|
+
titleChild.fg = isSel ? colors.accent : colors.text;
|
|
3068
3508
|
}
|
|
3069
3509
|
}
|
|
3070
3510
|
}
|
|
@@ -3093,7 +3533,6 @@ async function launchBrowser(theme_override) {
|
|
|
3093
3533
|
});
|
|
3094
3534
|
function hideMainContent() {
|
|
3095
3535
|
trendingTabBox.visible = false;
|
|
3096
|
-
gapBox.visible = false;
|
|
3097
3536
|
searchBox.visible = false;
|
|
3098
3537
|
toolbarText.visible = false;
|
|
3099
3538
|
body.visible = false;
|
|
@@ -3103,7 +3542,6 @@ async function launchBrowser(theme_override) {
|
|
|
3103
3542
|
trendingTabBox.visible = true;
|
|
3104
3543
|
} else {
|
|
3105
3544
|
searchBox.visible = true;
|
|
3106
|
-
gapBox.visible = true;
|
|
3107
3545
|
toolbarText.visible = true;
|
|
3108
3546
|
}
|
|
3109
3547
|
body.visible = true;
|
|
@@ -3129,7 +3567,6 @@ async function launchBrowser(theme_override) {
|
|
|
3129
3567
|
landingBox.visible = true;
|
|
3130
3568
|
body.visible = false;
|
|
3131
3569
|
trendingTabBox.visible = false;
|
|
3132
|
-
gapBox.visible = false;
|
|
3133
3570
|
searchBox.visible = false;
|
|
3134
3571
|
toolbarText.visible = false;
|
|
3135
3572
|
} else {
|
|
@@ -3170,7 +3607,6 @@ async function launchBrowser(theme_override) {
|
|
|
3170
3607
|
} else if (type === "update") {
|
|
3171
3608
|
updateDim.visible = true;
|
|
3172
3609
|
updateBox.visible = true;
|
|
3173
|
-
updateSelect.focus();
|
|
3174
3610
|
}
|
|
3175
3611
|
renderer.requestRender();
|
|
3176
3612
|
}
|
|
@@ -3178,9 +3614,7 @@ async function launchBrowser(theme_override) {
|
|
|
3178
3614
|
const helpBox = new BoxRenderable3(renderer, {
|
|
3179
3615
|
visible: false,
|
|
3180
3616
|
flexGrow: 1,
|
|
3181
|
-
backgroundColor: colors.bg
|
|
3182
|
-
borderBottom: true,
|
|
3183
|
-
borderBottomColor: colors.border
|
|
3617
|
+
backgroundColor: colors.bg
|
|
3184
3618
|
});
|
|
3185
3619
|
const helpScroll = new ScrollBoxRenderable3(renderer, {
|
|
3186
3620
|
flexGrow: 1,
|
|
@@ -3191,15 +3625,15 @@ async function launchBrowser(theme_override) {
|
|
|
3191
3625
|
viewportOptions: { backgroundColor: colors.bg },
|
|
3192
3626
|
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
3193
3627
|
scrollbarOptions: {
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3628
|
+
width: 1,
|
|
3629
|
+
trackOptions: {
|
|
3630
|
+
backgroundColor: colors.bg,
|
|
3631
|
+
foregroundColor: colors.muted
|
|
3632
|
+
}
|
|
3197
3633
|
}
|
|
3198
3634
|
});
|
|
3199
3635
|
for (const section of helpSections) {
|
|
3200
3636
|
const sectionBox = new BoxRenderable3(renderer, {
|
|
3201
|
-
borderBottom: true,
|
|
3202
|
-
borderBottomColor: colors.border,
|
|
3203
3637
|
title: section.title,
|
|
3204
3638
|
titleColor: colors[section.titleColor] ?? section.titleColor,
|
|
3205
3639
|
flexDirection: "column",
|
|
@@ -3213,7 +3647,7 @@ async function launchBrowser(theme_override) {
|
|
|
3213
3647
|
sectionBox.add(
|
|
3214
3648
|
new TextRenderable3(renderer, {
|
|
3215
3649
|
content: ` ${keys} ${row.action}`,
|
|
3216
|
-
|
|
3650
|
+
fg: colors.text
|
|
3217
3651
|
})
|
|
3218
3652
|
);
|
|
3219
3653
|
}
|
|
@@ -3221,7 +3655,7 @@ async function launchBrowser(theme_override) {
|
|
|
3221
3655
|
sectionBox.add(
|
|
3222
3656
|
new TextRenderable3(renderer, {
|
|
3223
3657
|
content: ` ${section.note}`,
|
|
3224
|
-
|
|
3658
|
+
fg: colors.muted
|
|
3225
3659
|
})
|
|
3226
3660
|
);
|
|
3227
3661
|
}
|
|
@@ -3233,8 +3667,6 @@ async function launchBrowser(theme_override) {
|
|
|
3233
3667
|
visible: false,
|
|
3234
3668
|
flexGrow: 1,
|
|
3235
3669
|
backgroundColor: colors.bg,
|
|
3236
|
-
borderBottom: true,
|
|
3237
|
-
borderBottomColor: colors.border,
|
|
3238
3670
|
title: " Search History ",
|
|
3239
3671
|
titleColor: colors.blue
|
|
3240
3672
|
});
|
|
@@ -3270,8 +3702,6 @@ async function launchBrowser(theme_override) {
|
|
|
3270
3702
|
visible: false,
|
|
3271
3703
|
flexGrow: 1,
|
|
3272
3704
|
backgroundColor: colors.bg,
|
|
3273
|
-
borderBottom: true,
|
|
3274
|
-
borderBottomColor: colors.border,
|
|
3275
3705
|
title: " Bookmarks ",
|
|
3276
3706
|
titleColor: colors.green
|
|
3277
3707
|
});
|
|
@@ -3307,8 +3737,6 @@ async function launchBrowser(theme_override) {
|
|
|
3307
3737
|
visible: false,
|
|
3308
3738
|
flexGrow: 1,
|
|
3309
3739
|
backgroundColor: colors.bg,
|
|
3310
|
-
borderBottom: true,
|
|
3311
|
-
borderBottomColor: colors.border,
|
|
3312
3740
|
title: " Saved Searches ",
|
|
3313
3741
|
titleColor: colors.yellow
|
|
3314
3742
|
});
|
|
@@ -3329,8 +3757,6 @@ async function launchBrowser(theme_override) {
|
|
|
3329
3757
|
visible: false,
|
|
3330
3758
|
flexGrow: 1,
|
|
3331
3759
|
backgroundColor: colors.bg,
|
|
3332
|
-
borderBottom: true,
|
|
3333
|
-
borderBottomColor: colors.border,
|
|
3334
3760
|
title: " Topics ",
|
|
3335
3761
|
titleColor: colors.purple ?? colors.blue
|
|
3336
3762
|
});
|
|
@@ -3366,8 +3792,6 @@ async function launchBrowser(theme_override) {
|
|
|
3366
3792
|
visible: false,
|
|
3367
3793
|
flexGrow: 1,
|
|
3368
3794
|
backgroundColor: colors.bg,
|
|
3369
|
-
borderBottom: true,
|
|
3370
|
-
borderBottomColor: colors.border,
|
|
3371
3795
|
title: " Export ",
|
|
3372
3796
|
titleColor: colors.green
|
|
3373
3797
|
});
|
|
@@ -3405,15 +3829,13 @@ async function launchBrowser(theme_override) {
|
|
|
3405
3829
|
visible: false,
|
|
3406
3830
|
flexGrow: 1,
|
|
3407
3831
|
backgroundColor: colors.bg,
|
|
3408
|
-
borderBottom: true,
|
|
3409
|
-
borderBottomColor: colors.border,
|
|
3410
3832
|
title: " Comparison ",
|
|
3411
3833
|
titleColor: colors.yellow
|
|
3412
3834
|
});
|
|
3413
3835
|
const compareText = new TextRenderable3(renderer, {
|
|
3414
3836
|
content: "",
|
|
3415
|
-
|
|
3416
|
-
|
|
3837
|
+
fg: colors.text,
|
|
3838
|
+
bg: colors.bg
|
|
3417
3839
|
});
|
|
3418
3840
|
compareBox.add(compareText);
|
|
3419
3841
|
root.add(compareBox);
|
|
@@ -3421,15 +3843,13 @@ async function launchBrowser(theme_override) {
|
|
|
3421
3843
|
visible: false,
|
|
3422
3844
|
flexGrow: 1,
|
|
3423
3845
|
backgroundColor: colors.bg,
|
|
3424
|
-
borderBottom: true,
|
|
3425
|
-
borderBottomColor: colors.border,
|
|
3426
3846
|
title: " Notifications ",
|
|
3427
3847
|
titleColor: colors.yellow
|
|
3428
3848
|
});
|
|
3429
3849
|
const notifsText = new TextRenderable3(renderer, {
|
|
3430
3850
|
content: "(no notifications)",
|
|
3431
|
-
|
|
3432
|
-
|
|
3851
|
+
fg: colors.text,
|
|
3852
|
+
bg: colors.bg
|
|
3433
3853
|
});
|
|
3434
3854
|
notifsBox.add(notifsText);
|
|
3435
3855
|
root.add(notifsBox);
|
|
@@ -3445,8 +3865,6 @@ async function launchBrowser(theme_override) {
|
|
|
3445
3865
|
visible: false,
|
|
3446
3866
|
flexGrow: 1,
|
|
3447
3867
|
backgroundColor: colors.bg,
|
|
3448
|
-
borderBottom: true,
|
|
3449
|
-
borderBottomColor: colors.border,
|
|
3450
3868
|
title: " Share Repo ",
|
|
3451
3869
|
titleColor: colors.blue
|
|
3452
3870
|
});
|
|
@@ -3547,11 +3965,9 @@ async function launchBrowser(theme_override) {
|
|
|
3547
3965
|
});
|
|
3548
3966
|
const leaderFooter = new TextRenderable3(renderer, {
|
|
3549
3967
|
content: " \u2191\u2193 select Enter run Esc/q close ",
|
|
3550
|
-
|
|
3551
|
-
|
|
3968
|
+
fg: colors.muted,
|
|
3969
|
+
bg: colors.surfaceDim,
|
|
3552
3970
|
height: 1,
|
|
3553
|
-
borderTop: true,
|
|
3554
|
-
borderTopColor: colors.border,
|
|
3555
3971
|
paddingX: 2
|
|
3556
3972
|
});
|
|
3557
3973
|
leaderBox.add(leaderSelect);
|
|
@@ -3589,8 +4005,8 @@ async function launchBrowser(theme_override) {
|
|
|
3589
4005
|
});
|
|
3590
4006
|
const updateText = new TextRenderable3(renderer, {
|
|
3591
4007
|
content: "",
|
|
3592
|
-
|
|
3593
|
-
|
|
4008
|
+
fg: colors.accent,
|
|
4009
|
+
bg: colors.surfaceDim
|
|
3594
4010
|
});
|
|
3595
4011
|
let updateSelectedOption = 0;
|
|
3596
4012
|
const updateOptionsRow = new BoxRenderable3(renderer, {
|
|
@@ -3611,8 +4027,8 @@ async function launchBrowser(theme_override) {
|
|
|
3611
4027
|
});
|
|
3612
4028
|
const updateNowText = new TextRenderable3(renderer, {
|
|
3613
4029
|
content: " Update Now ",
|
|
3614
|
-
|
|
3615
|
-
|
|
4030
|
+
fg: updateSelectedOption === 0 ? colors.yellow : colors.text,
|
|
4031
|
+
bg: colors.surfaceDim
|
|
3616
4032
|
});
|
|
3617
4033
|
updateNowBox.add(updateNowText);
|
|
3618
4034
|
const laterBox = new BoxRenderable3(renderer, {
|
|
@@ -3626,8 +4042,8 @@ async function launchBrowser(theme_override) {
|
|
|
3626
4042
|
});
|
|
3627
4043
|
const laterText = new TextRenderable3(renderer, {
|
|
3628
4044
|
content: " Later",
|
|
3629
|
-
|
|
3630
|
-
|
|
4045
|
+
fg: updateSelectedOption === 1 ? colors.yellow : colors.text,
|
|
4046
|
+
bg: colors.surfaceDim
|
|
3631
4047
|
});
|
|
3632
4048
|
laterBox.add(laterText);
|
|
3633
4049
|
const neverBox = new BoxRenderable3(renderer, {
|
|
@@ -3641,25 +4057,25 @@ async function launchBrowser(theme_override) {
|
|
|
3641
4057
|
});
|
|
3642
4058
|
const neverText = new TextRenderable3(renderer, {
|
|
3643
4059
|
content: " Don't show again",
|
|
3644
|
-
|
|
3645
|
-
|
|
4060
|
+
fg: updateSelectedOption === 2 ? colors.yellow : colors.text,
|
|
4061
|
+
bg: colors.surfaceDim
|
|
3646
4062
|
});
|
|
3647
4063
|
neverBox.add(neverText);
|
|
3648
4064
|
function renderUpdateOptions() {
|
|
3649
4065
|
updateNowBox.borderColor = updateSelectedOption === 0 ? colors.yellow : colors.border;
|
|
3650
4066
|
laterBox.borderColor = updateSelectedOption === 1 ? colors.yellow : colors.border;
|
|
3651
4067
|
neverBox.borderColor = updateSelectedOption === 2 ? colors.yellow : colors.border;
|
|
3652
|
-
updateNowText.
|
|
3653
|
-
laterText.
|
|
3654
|
-
neverText.
|
|
4068
|
+
updateNowText.fg = updateSelectedOption === 0 ? colors.yellow : colors.text;
|
|
4069
|
+
laterText.fg = updateSelectedOption === 1 ? colors.yellow : colors.text;
|
|
4070
|
+
neverText.fg = updateSelectedOption === 2 ? colors.yellow : colors.text;
|
|
3655
4071
|
renderer.requestRender();
|
|
3656
4072
|
}
|
|
3657
4073
|
updateOptionsRow.add(updateNowBox);
|
|
3658
4074
|
updateOptionsRow.add(laterBox);
|
|
3659
4075
|
const updateFooter = new TextRenderable3(renderer, {
|
|
3660
4076
|
content: " \u2190\u2192 select Enter Esc/q close",
|
|
3661
|
-
|
|
3662
|
-
|
|
4077
|
+
fg: colors.muted,
|
|
4078
|
+
bg: colors.surfaceDim,
|
|
3663
4079
|
height: 1,
|
|
3664
4080
|
paddingX: 1
|
|
3665
4081
|
});
|
|
@@ -3979,13 +4395,7 @@ async function launchBrowser(theme_override) {
|
|
|
3979
4395
|
name: "Quit",
|
|
3980
4396
|
description: "Exit ghfind",
|
|
3981
4397
|
action: () => {
|
|
3982
|
-
saveSession(
|
|
3983
|
-
mode: currentMode,
|
|
3984
|
-
query: currentQueryInput,
|
|
3985
|
-
sort: currentSort,
|
|
3986
|
-
limit: currentLimit,
|
|
3987
|
-
trendingTab
|
|
3988
|
-
});
|
|
4398
|
+
saveSession(buildSessionState());
|
|
3989
4399
|
cleanup();
|
|
3990
4400
|
}
|
|
3991
4401
|
}
|
|
@@ -4099,7 +4509,7 @@ ${notesBlock}`;
|
|
|
4099
4509
|
saveSearch(
|
|
4100
4510
|
name,
|
|
4101
4511
|
currentQueryInput,
|
|
4102
|
-
currentMode,
|
|
4512
|
+
currentMode === "trending" ? "trending" : "search",
|
|
4103
4513
|
currentSort,
|
|
4104
4514
|
currentLimit,
|
|
4105
4515
|
currentMode === "trending" ? trendingTab : void 0
|
|
@@ -4133,8 +4543,6 @@ ${notesBlock}`;
|
|
|
4133
4543
|
visible: false,
|
|
4134
4544
|
flexGrow: 1,
|
|
4135
4545
|
backgroundColor: colors.surface,
|
|
4136
|
-
borderBottom: true,
|
|
4137
|
-
borderBottomColor: colors.border,
|
|
4138
4546
|
title: " Org profile ",
|
|
4139
4547
|
titleColor: colors.green
|
|
4140
4548
|
});
|
|
@@ -4147,22 +4555,24 @@ ${notesBlock}`;
|
|
|
4147
4555
|
viewportOptions: { backgroundColor: colors.bg },
|
|
4148
4556
|
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
4149
4557
|
scrollbarOptions: {
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4558
|
+
width: 1,
|
|
4559
|
+
trackOptions: {
|
|
4560
|
+
backgroundColor: colors.bg,
|
|
4561
|
+
foregroundColor: colors.muted
|
|
4562
|
+
}
|
|
4153
4563
|
}
|
|
4154
4564
|
});
|
|
4155
4565
|
const orgText = new TextRenderable3(renderer, {
|
|
4156
4566
|
content: "",
|
|
4157
|
-
|
|
4158
|
-
|
|
4567
|
+
fg: colors.text,
|
|
4568
|
+
bg: colors.bg
|
|
4159
4569
|
});
|
|
4160
4570
|
orgScroll.add(orgText);
|
|
4161
4571
|
orgBox.add(orgScroll);
|
|
4162
4572
|
const orgFooter = new TextRenderable3(renderer, {
|
|
4163
4573
|
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
4164
|
-
|
|
4165
|
-
|
|
4574
|
+
fg: colors.muted,
|
|
4575
|
+
bg: colors.bg,
|
|
4166
4576
|
height: 1,
|
|
4167
4577
|
paddingX: 1
|
|
4168
4578
|
});
|
|
@@ -4229,8 +4639,6 @@ ${notesBlock}`;
|
|
|
4229
4639
|
visible: false,
|
|
4230
4640
|
flexGrow: 1,
|
|
4231
4641
|
backgroundColor: colors.surface,
|
|
4232
|
-
borderBottom: true,
|
|
4233
|
-
borderBottomColor: colors.border,
|
|
4234
4642
|
title: " README ",
|
|
4235
4643
|
titleColor: colors.green
|
|
4236
4644
|
});
|
|
@@ -4243,22 +4651,24 @@ ${notesBlock}`;
|
|
|
4243
4651
|
viewportOptions: { backgroundColor: colors.bg },
|
|
4244
4652
|
contentOptions: { backgroundColor: colors.bg, flexDirection: "column" },
|
|
4245
4653
|
scrollbarOptions: {
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4654
|
+
width: 1,
|
|
4655
|
+
trackOptions: {
|
|
4656
|
+
backgroundColor: colors.bg,
|
|
4657
|
+
foregroundColor: colors.muted
|
|
4658
|
+
}
|
|
4249
4659
|
}
|
|
4250
4660
|
});
|
|
4251
4661
|
const readmeText = new TextRenderable3(renderer, {
|
|
4252
4662
|
content: "",
|
|
4253
|
-
|
|
4254
|
-
|
|
4663
|
+
fg: colors.text,
|
|
4664
|
+
bg: colors.bg
|
|
4255
4665
|
});
|
|
4256
4666
|
readmeScroll.add(readmeText);
|
|
4257
4667
|
readmeBox.add(readmeScroll);
|
|
4258
4668
|
const readmeFooter = new TextRenderable3(renderer, {
|
|
4259
4669
|
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
4260
|
-
|
|
4261
|
-
|
|
4670
|
+
fg: colors.muted,
|
|
4671
|
+
bg: colors.bg,
|
|
4262
4672
|
height: 1,
|
|
4263
4673
|
paddingX: 1
|
|
4264
4674
|
});
|
|
@@ -4303,12 +4713,9 @@ ${notesBlock}`;
|
|
|
4303
4713
|
}
|
|
4304
4714
|
const statusBar = new TextRenderable3(renderer, {
|
|
4305
4715
|
content: " Ready. Press Enter to search.",
|
|
4306
|
-
|
|
4307
|
-
color: colors.muted,
|
|
4716
|
+
fg: colors.muted,
|
|
4308
4717
|
height: 1,
|
|
4309
|
-
paddingX: 1
|
|
4310
|
-
borderTop: true,
|
|
4311
|
-
borderTopColor: colors.border
|
|
4718
|
+
paddingX: 1
|
|
4312
4719
|
});
|
|
4313
4720
|
root.add(statusBar);
|
|
4314
4721
|
const statusMgr = new StatusManager((text) => {
|
|
@@ -4325,6 +4732,16 @@ ${notesBlock}`;
|
|
|
4325
4732
|
function setStatus(msg) {
|
|
4326
4733
|
statusMgr.set("idle", msg);
|
|
4327
4734
|
}
|
|
4735
|
+
function buildSessionState() {
|
|
4736
|
+
const mode = currentMode === "trending" ? "trending" : currentMode === "packages" ? "packages" : "search";
|
|
4737
|
+
return {
|
|
4738
|
+
mode,
|
|
4739
|
+
query: currentQueryInput,
|
|
4740
|
+
sort: currentSort,
|
|
4741
|
+
limit: currentLimit,
|
|
4742
|
+
trendingTab
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4328
4745
|
function setToolbar() {
|
|
4329
4746
|
const sort = currentMode === "packages" ? currentPackageSort : currentSort;
|
|
4330
4747
|
toolbarText.content = formatToolbar(sort, currentLimit, totalCount);
|
|
@@ -4452,7 +4869,9 @@ ${notesBlock}`;
|
|
|
4452
4869
|
),
|
|
4453
4870
|
fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}`, {
|
|
4454
4871
|
headers
|
|
4455
|
-
}).then(
|
|
4872
|
+
}).then(
|
|
4873
|
+
(r) => r.ok ? r.clone().json() : null
|
|
4874
|
+
)
|
|
4456
4875
|
]);
|
|
4457
4876
|
let chartSection = "";
|
|
4458
4877
|
if (chartRes.ok) {
|
|
@@ -4590,7 +5009,6 @@ ${notesBlock}`;
|
|
|
4590
5009
|
function showSearchMode() {
|
|
4591
5010
|
currentMode = "search";
|
|
4592
5011
|
trendingTabBox.visible = false;
|
|
4593
|
-
gapBox.visible = false;
|
|
4594
5012
|
searchBox.visible = true;
|
|
4595
5013
|
toolbarText.visible = true;
|
|
4596
5014
|
body.visible = true;
|
|
@@ -4601,7 +5019,6 @@ ${notesBlock}`;
|
|
|
4601
5019
|
function showPackagesMode() {
|
|
4602
5020
|
currentMode = "packages";
|
|
4603
5021
|
trendingTabBox.visible = false;
|
|
4604
|
-
gapBox.visible = false;
|
|
4605
5022
|
searchBox.visible = true;
|
|
4606
5023
|
toolbarText.visible = true;
|
|
4607
5024
|
body.visible = true;
|
|
@@ -5077,13 +5494,7 @@ ${pack.description ?? ""}`;
|
|
|
5077
5494
|
toggleGraph();
|
|
5078
5495
|
return;
|
|
5079
5496
|
}
|
|
5080
|
-
saveSession(
|
|
5081
|
-
mode: currentMode,
|
|
5082
|
-
query: currentQueryInput,
|
|
5083
|
-
sort: currentSort,
|
|
5084
|
-
limit: currentLimit,
|
|
5085
|
-
trendingTab
|
|
5086
|
-
});
|
|
5497
|
+
saveSession(buildSessionState());
|
|
5087
5498
|
cleanup();
|
|
5088
5499
|
return;
|
|
5089
5500
|
}
|
|
@@ -5092,11 +5503,7 @@ ${pack.description ?? ""}`;
|
|
|
5092
5503
|
return;
|
|
5093
5504
|
}
|
|
5094
5505
|
if (key.name === "space" && !searchInput.focused) {
|
|
5095
|
-
|
|
5096
|
-
showOverlay("none");
|
|
5097
|
-
} else {
|
|
5098
|
-
showLeaderMenu();
|
|
5099
|
-
}
|
|
5506
|
+
showLeaderMenu();
|
|
5100
5507
|
return;
|
|
5101
5508
|
}
|
|
5102
5509
|
if (key.name === "t" && !searchInput.focused) {
|
|
@@ -5106,19 +5513,20 @@ ${pack.description ?? ""}`;
|
|
|
5106
5513
|
const next = themes[(idx + 1) % themes.length];
|
|
5107
5514
|
const nextTheme = loadTheme(next);
|
|
5108
5515
|
Object.assign(colors, nextTheme);
|
|
5516
|
+
if (terminalBg) {
|
|
5517
|
+
colors.bg = terminalBg;
|
|
5518
|
+
Object.assign(colors, deriveSurfaceLayers(terminalBg));
|
|
5519
|
+
}
|
|
5520
|
+
renderer.setBackgroundColor(colors.bg);
|
|
5109
5521
|
config.theme = next;
|
|
5110
5522
|
saveConfig(config);
|
|
5111
5523
|
renderer.requestRender();
|
|
5112
5524
|
return;
|
|
5113
5525
|
}
|
|
5114
5526
|
if (key.name === "c" && !searchInput.focused) {
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
toggleCompareOnSelected();
|
|
5119
|
-
refreshCompare();
|
|
5120
|
-
showOverlay("compare");
|
|
5121
|
-
}
|
|
5527
|
+
toggleCompareOnSelected();
|
|
5528
|
+
refreshCompare();
|
|
5529
|
+
showOverlay("compare");
|
|
5122
5530
|
return;
|
|
5123
5531
|
}
|
|
5124
5532
|
if (key.name === "?" || key.name === "h" && key.ctrl) {
|
|
@@ -5242,7 +5650,7 @@ if (import.meta.main && false) {
|
|
|
5242
5650
|
// src/cli.ts
|
|
5243
5651
|
process.env.GHFIND_CLI_RUN = "1";
|
|
5244
5652
|
var __dirname3 = dirname4(fileURLToPath4(import.meta.url));
|
|
5245
|
-
var PKG_DIR3 =
|
|
5653
|
+
var PKG_DIR3 = join6(__dirname3, "..");
|
|
5246
5654
|
function parseArgs(args) {
|
|
5247
5655
|
const flags = {
|
|
5248
5656
|
query: "",
|
|
@@ -5259,6 +5667,7 @@ function parseArgs(args) {
|
|
|
5259
5667
|
releases: false,
|
|
5260
5668
|
interval: 300,
|
|
5261
5669
|
init: false,
|
|
5670
|
+
login: false,
|
|
5262
5671
|
version: false,
|
|
5263
5672
|
help: false,
|
|
5264
5673
|
pkg: false,
|
|
@@ -5306,6 +5715,9 @@ function parseArgs(args) {
|
|
|
5306
5715
|
case "init":
|
|
5307
5716
|
flags.init = true;
|
|
5308
5717
|
break;
|
|
5718
|
+
case "login":
|
|
5719
|
+
flags.login = true;
|
|
5720
|
+
break;
|
|
5309
5721
|
case "--limit": {
|
|
5310
5722
|
const parsed = parseInt(args[++i]);
|
|
5311
5723
|
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
@@ -5397,6 +5809,7 @@ Usage:
|
|
|
5397
5809
|
ghfind pkg <query> Search npm packages, text list
|
|
5398
5810
|
ghfind --watch <query> Watch mode (poll every Ns)
|
|
5399
5811
|
ghfind init Run setup wizard
|
|
5812
|
+
ghfind login Import the gh CLI token or paste one
|
|
5400
5813
|
ghfind --completion <shell> Print completion script (bash|zsh|fish)
|
|
5401
5814
|
ghfind --version Print version
|
|
5402
5815
|
ghfind --doctor Run environment diagnostics (troubleshooting)
|
|
@@ -5423,8 +5836,8 @@ Options:
|
|
|
5423
5836
|
if (flags.completion) {
|
|
5424
5837
|
const shell = flags.completion;
|
|
5425
5838
|
try {
|
|
5426
|
-
const content =
|
|
5427
|
-
|
|
5839
|
+
const content = readFileSync6(
|
|
5840
|
+
join6(PKG_DIR3, "completions", `ghfind.${shell}`),
|
|
5428
5841
|
"utf-8"
|
|
5429
5842
|
);
|
|
5430
5843
|
console.log(content);
|
|
@@ -5438,6 +5851,10 @@ Options:
|
|
|
5438
5851
|
await runInitWizard();
|
|
5439
5852
|
return;
|
|
5440
5853
|
}
|
|
5854
|
+
if (flags.login) {
|
|
5855
|
+
await runLoginWizard();
|
|
5856
|
+
return;
|
|
5857
|
+
}
|
|
5441
5858
|
if (flags.pkg) {
|
|
5442
5859
|
const interactive = !flags.query && !flags.json && !flags.csv && !flags.markdown && !flags.count && !flags.format;
|
|
5443
5860
|
if (interactive) {
|
|
@@ -5699,7 +6116,9 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
5699
6116
|
}
|
|
5700
6117
|
main().catch((err) => {
|
|
5701
6118
|
if (err instanceof Error && err.name === "SearchCliError") {
|
|
5702
|
-
console.error(
|
|
6119
|
+
console.error(
|
|
6120
|
+
err instanceof SearchCliError ? err.userMessage : err.message
|
|
6121
|
+
);
|
|
5703
6122
|
} else {
|
|
5704
6123
|
console.error(err instanceof Error ? err.message : String(err));
|
|
5705
6124
|
reportError(err);
|