github-search-cli 9.8.2 → 9.8.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 +57 -0
- package/README.md +35 -18
- package/dist/cli.js +360 -122
- package/package.json +1 -1
- package/release-notes/9.8.3.md +98 -0
- package/src/errors.ts +27 -0
- package/src/help.ts +11 -0
- package/src/image-loader.ts +57 -2
- package/src/markdown-images.ts +93 -4
- package/src/markdown-view.ts +49 -20
- package/src/mcp-server.ts +17 -1
- package/src/search.ts +19 -2
- package/src/session.ts +58 -2
- package/src/tui.ts +181 -33
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,20 @@ var NetworkError = class extends SearchCliError {
|
|
|
16
16
|
this.name = "NetworkError";
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
+
var AuthError = class extends SearchCliError {
|
|
20
|
+
constructor() {
|
|
21
|
+
const msg = "\u26A0 Bad GitHub token (401). Press [t] to fix it, [u] to search without one";
|
|
22
|
+
super("GitHub API returned 401 (invalid or revoked token)", msg);
|
|
23
|
+
this.name = "AuthError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var ForbiddenError = class extends SearchCliError {
|
|
27
|
+
constructor() {
|
|
28
|
+
const msg = "\u26A0 Forbidden (403). If your org enforces SSO, authorize the token; [t] to change it";
|
|
29
|
+
super("GitHub API returned 403 (token lacks access)", msg);
|
|
30
|
+
this.name = "ForbiddenError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
19
33
|
var RateLimitError = class extends SearchCliError {
|
|
20
34
|
constructor(hasToken, resetSeconds) {
|
|
21
35
|
const msg = hasToken ? `\u26A0 API rate limit exceeded (5,000/hr). Resets in ${resetSeconds ?? "?"}s. [r]etry [c]hange token` : "\u26A0 Rate limited (60/hr). Set GITHUB_TOKEN for 5,000/hr. [r]etry";
|
|
@@ -430,7 +444,14 @@ var GitHubSearchAdapter = class {
|
|
|
430
444
|
this.logger.error(
|
|
431
445
|
`[github] non-rate-limit 403: ${body.slice(0, 200)}`
|
|
432
446
|
);
|
|
433
|
-
throw new
|
|
447
|
+
throw new ForbiddenError();
|
|
448
|
+
}
|
|
449
|
+
if (res.status === 401) {
|
|
450
|
+
const body = await res.text().catch(() => "");
|
|
451
|
+
this.logger.error(
|
|
452
|
+
`[github] auth rejected (401): ${body.slice(0, 200)}`
|
|
453
|
+
);
|
|
454
|
+
throw new AuthError();
|
|
434
455
|
}
|
|
435
456
|
if (!res.ok) {
|
|
436
457
|
const body = await res.text().catch(() => "");
|
|
@@ -467,6 +488,7 @@ var GitHubSearchAdapter = class {
|
|
|
467
488
|
lastErr = void 0;
|
|
468
489
|
break;
|
|
469
490
|
} catch (err) {
|
|
491
|
+
if (err instanceof RateLimitError) throw err;
|
|
470
492
|
lastErr = err;
|
|
471
493
|
if (rateLimited) break;
|
|
472
494
|
}
|
|
@@ -1715,7 +1737,7 @@ var cached = null;
|
|
|
1715
1737
|
function getVersion() {
|
|
1716
1738
|
if (cached) return cached;
|
|
1717
1739
|
if (true) {
|
|
1718
|
-
cached = "9.8.
|
|
1740
|
+
cached = "9.8.3";
|
|
1719
1741
|
return cached;
|
|
1720
1742
|
}
|
|
1721
1743
|
try {
|
|
@@ -2865,6 +2887,122 @@ function comparisonMarkdown(repos) {
|
|
|
2865
2887
|
return [header, sep, ...rows].join("\n");
|
|
2866
2888
|
}
|
|
2867
2889
|
|
|
2890
|
+
// src/markdown-images.ts
|
|
2891
|
+
var MIN_IMAGE_PIXELS = 100;
|
|
2892
|
+
var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
2893
|
+
var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
|
|
2894
|
+
var LENGTH_ATTR = /\b(width|height)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
2895
|
+
var PIXEL_VALUE = /^(\d+)(?:px)?$/i;
|
|
2896
|
+
var HTML_TAG = /<\/?[a-z][^>]*>/gi;
|
|
2897
|
+
function declaredImageSize(tag) {
|
|
2898
|
+
const values = {};
|
|
2899
|
+
for (const [, name, dq, sq] of tag.matchAll(LENGTH_ATTR)) {
|
|
2900
|
+
const raw = dq ?? sq ?? "";
|
|
2901
|
+
const match = PIXEL_VALUE.exec(raw.trim());
|
|
2902
|
+
if (!match) continue;
|
|
2903
|
+
const n = Number.parseInt(match[1], 10);
|
|
2904
|
+
if (Number.isFinite(n) && n > 0) {
|
|
2905
|
+
values[name] = n;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
if (values.width === void 0 && values.height === void 0)
|
|
2909
|
+
return void 0;
|
|
2910
|
+
return {
|
|
2911
|
+
width: values.width ?? values.height ?? 0,
|
|
2912
|
+
height: values.height ?? values.width ?? 0
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
function declaresTooSmall(size) {
|
|
2916
|
+
if (!size) return false;
|
|
2917
|
+
return size.width < MIN_IMAGE_PIXELS || size.height < MIN_IMAGE_PIXELS;
|
|
2918
|
+
}
|
|
2919
|
+
function imagesFromHtml(raw) {
|
|
2920
|
+
if (!raw || !/<img\b/i.test(raw)) return [];
|
|
2921
|
+
const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
|
|
2922
|
+
if (withoutTags) return [];
|
|
2923
|
+
const refs = [];
|
|
2924
|
+
for (const match of raw.matchAll(IMG_TAG)) {
|
|
2925
|
+
const tag = match[0];
|
|
2926
|
+
const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
|
|
2927
|
+
refs.push({ href: match[1], alt, declared: declaredImageSize(tag) });
|
|
2928
|
+
}
|
|
2929
|
+
return refs;
|
|
2930
|
+
}
|
|
2931
|
+
function inlinePlainText(tokens) {
|
|
2932
|
+
if (!tokens) return "";
|
|
2933
|
+
let out = "";
|
|
2934
|
+
for (const token of tokens) {
|
|
2935
|
+
if (token.type === "image") continue;
|
|
2936
|
+
if (token.type === "html") {
|
|
2937
|
+
out += (token.raw ?? "").replace(HTML_TAG, "");
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (token.type === "br") {
|
|
2941
|
+
out += "\n";
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
if (token.text && (!token.tokens || token.tokens.length === 0)) {
|
|
2945
|
+
out += token.text;
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2948
|
+
if (token.tokens?.length) {
|
|
2949
|
+
out += inlinePlainText(token.tokens);
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
if (token.raw) out += token.raw.replace(HTML_TAG, "");
|
|
2953
|
+
}
|
|
2954
|
+
return out;
|
|
2955
|
+
}
|
|
2956
|
+
function extractImageRefs(token) {
|
|
2957
|
+
const images = [];
|
|
2958
|
+
let linkUrl;
|
|
2959
|
+
const collect = (inline2) => {
|
|
2960
|
+
if (inline2.type === "image") {
|
|
2961
|
+
if (inline2.href)
|
|
2962
|
+
images.push({ href: inline2.href, alt: inline2.text ?? "", linkUrl });
|
|
2963
|
+
return true;
|
|
2964
|
+
}
|
|
2965
|
+
if (inline2.type === "link" || inline2.type === "linkReference") {
|
|
2966
|
+
if (inline2.tokens?.length) {
|
|
2967
|
+
const outer = linkUrl;
|
|
2968
|
+
linkUrl = inline2.href || outer;
|
|
2969
|
+
const ok = inline2.tokens.every(collect);
|
|
2970
|
+
linkUrl = outer;
|
|
2971
|
+
return ok;
|
|
2972
|
+
}
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
if (inline2.type === "html") {
|
|
2976
|
+
const refs = imagesFromHtml(inline2.raw);
|
|
2977
|
+
if (refs.length > 0) {
|
|
2978
|
+
images.push(...refs);
|
|
2979
|
+
return true;
|
|
2980
|
+
}
|
|
2981
|
+
return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
|
|
2982
|
+
}
|
|
2983
|
+
if (inline2.type === "text" || inline2.type === "escape") {
|
|
2984
|
+
return !(inline2.text ?? "").trim();
|
|
2985
|
+
}
|
|
2986
|
+
return false;
|
|
2987
|
+
};
|
|
2988
|
+
if (token.type === "image" && token.href) {
|
|
2989
|
+
return [{ href: token.href, alt: token.text ?? "" }];
|
|
2990
|
+
}
|
|
2991
|
+
if (token.type === "html") {
|
|
2992
|
+
const refs = imagesFromHtml(token.raw);
|
|
2993
|
+
return refs.length > 0 ? dropTooSmall(refs) : null;
|
|
2994
|
+
}
|
|
2995
|
+
const inline = token.tokens ?? [];
|
|
2996
|
+
if (inline.length === 0) return null;
|
|
2997
|
+
for (const child of inline) {
|
|
2998
|
+
if (!collect(child)) return null;
|
|
2999
|
+
}
|
|
3000
|
+
return images.length > 0 ? dropTooSmall(images) : null;
|
|
3001
|
+
}
|
|
3002
|
+
function dropTooSmall(refs) {
|
|
3003
|
+
return refs.filter((image) => !declaresTooSmall(image.declared));
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2868
3006
|
// src/image-loader.ts
|
|
2869
3007
|
var MAX_IMAGE_BYTES = 6 * 1024 * 1024;
|
|
2870
3008
|
var BADGE_HOST = /(^|\.)(shields\.io|badgen\.net|badge\.fury\.io|codecov\.io|coveralls\.io|travis-ci\.(org|com)|snyk\.io|sonarcloud\.io|david-dm\.(org|io))$/i;
|
|
@@ -3032,6 +3170,23 @@ async function loadImageBytes(url, opts) {
|
|
|
3032
3170
|
function readmeBaseUrl(owner, repo, branch, path = "README.md") {
|
|
3033
3171
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
|
3034
3172
|
}
|
|
3173
|
+
async function imageSizeFromBytes(bytes) {
|
|
3174
|
+
const ImageCtor = globalThis.Bun?.Image;
|
|
3175
|
+
if (!ImageCtor) return void 0;
|
|
3176
|
+
try {
|
|
3177
|
+
const meta = await new ImageCtor(bytes).metadata();
|
|
3178
|
+
if (!meta.width || !meta.height) return void 0;
|
|
3179
|
+
return { width: meta.width, height: meta.height };
|
|
3180
|
+
} catch {
|
|
3181
|
+
return void 0;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
async function shouldRenderImage(bytes, declared) {
|
|
3185
|
+
if (declared) return !declaresTooSmall(declared);
|
|
3186
|
+
const measured = await imageSizeFromBytes(bytes);
|
|
3187
|
+
if (!measured) return true;
|
|
3188
|
+
return !declaresTooSmall(measured);
|
|
3189
|
+
}
|
|
3035
3190
|
|
|
3036
3191
|
// src/readme.ts
|
|
3037
3192
|
var CANDIDATES = [
|
|
@@ -3340,6 +3495,17 @@ function buildHelpSections() {
|
|
|
3340
3495
|
{ keys: "PageUp", action: "Scroll to top of results" }
|
|
3341
3496
|
]
|
|
3342
3497
|
},
|
|
3498
|
+
{
|
|
3499
|
+
title: " Error Recovery ",
|
|
3500
|
+
titleColor: "red",
|
|
3501
|
+
rows: [
|
|
3502
|
+
{ keys: "r", action: "Retry after an error" },
|
|
3503
|
+
{ keys: "t", action: "Fix GitHub token (401/403)" },
|
|
3504
|
+
{ keys: "u", action: "Search without a token (401)" },
|
|
3505
|
+
{ keys: "c", action: "Change token (rate limit)" }
|
|
3506
|
+
],
|
|
3507
|
+
note: "Active only while the matching error is in the status bar."
|
|
3508
|
+
},
|
|
3343
3509
|
{
|
|
3344
3510
|
title: " Trending Mode ",
|
|
3345
3511
|
titleColor: "yellow",
|
|
@@ -3552,9 +3718,35 @@ function touchSavedSearch(name) {
|
|
|
3552
3718
|
|
|
3553
3719
|
// src/session.ts
|
|
3554
3720
|
var SESSION_FILE = "session.json";
|
|
3721
|
+
var MAX_QUERY_LENGTH = 200;
|
|
3722
|
+
function sanitizeQuery(query) {
|
|
3723
|
+
if (typeof query !== "string") return void 0;
|
|
3724
|
+
const trimmed = query.trim();
|
|
3725
|
+
if (trimmed.length === 0 || trimmed.length > MAX_QUERY_LENGTH) {
|
|
3726
|
+
return void 0;
|
|
3727
|
+
}
|
|
3728
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
3729
|
+
return void 0;
|
|
3730
|
+
}
|
|
3731
|
+
return trimmed;
|
|
3732
|
+
}
|
|
3733
|
+
function sanitizeSession(state) {
|
|
3734
|
+
if (!state || typeof state !== "object") return null;
|
|
3735
|
+
const mode = state.mode === "trending" || state.mode === "packages" || state.mode === "search" ? state.mode : "search";
|
|
3736
|
+
const trendingTab = typeof state.trendingTab === "string" && state.trendingTab.length > 0 ? state.trendingTab : "This Week";
|
|
3737
|
+
const limit = typeof state.limit === "number" && Number.isFinite(state.limit) ? state.limit : 50;
|
|
3738
|
+
const sort = typeof state.sort === "string" ? state.sort : "best-match";
|
|
3739
|
+
return {
|
|
3740
|
+
mode,
|
|
3741
|
+
query: sanitizeQuery(state.query) ?? "",
|
|
3742
|
+
sort,
|
|
3743
|
+
limit,
|
|
3744
|
+
trendingTab
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3555
3747
|
function saveSession(state) {
|
|
3556
3748
|
try {
|
|
3557
|
-
writeJSON(SESSION_FILE, state);
|
|
3749
|
+
writeJSON(SESSION_FILE, sanitizeSession(state));
|
|
3558
3750
|
} catch (err) {
|
|
3559
3751
|
if (process.env.DEBUG)
|
|
3560
3752
|
console.error(
|
|
@@ -3563,7 +3755,7 @@ function saveSession(state) {
|
|
|
3563
3755
|
}
|
|
3564
3756
|
}
|
|
3565
3757
|
function restoreSession() {
|
|
3566
|
-
return readJSON(SESSION_FILE, null);
|
|
3758
|
+
return sanitizeSession(readJSON(SESSION_FILE, null));
|
|
3567
3759
|
}
|
|
3568
3760
|
|
|
3569
3761
|
// src/markdown-view.ts
|
|
@@ -3869,83 +4061,6 @@ async function rasterizeImage(bytes, opts) {
|
|
|
3869
4061
|
}
|
|
3870
4062
|
}
|
|
3871
4063
|
|
|
3872
|
-
// src/markdown-images.ts
|
|
3873
|
-
var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
3874
|
-
var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
|
|
3875
|
-
var HTML_TAG = /<\/?[a-z][^>]*>/gi;
|
|
3876
|
-
function imagesFromHtml(raw) {
|
|
3877
|
-
if (!raw || !/<img\b/i.test(raw)) return [];
|
|
3878
|
-
const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
|
|
3879
|
-
if (withoutTags) return [];
|
|
3880
|
-
const refs = [];
|
|
3881
|
-
for (const match of raw.matchAll(IMG_TAG)) {
|
|
3882
|
-
const tag = match[0];
|
|
3883
|
-
const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
|
|
3884
|
-
refs.push({ href: match[1], alt });
|
|
3885
|
-
}
|
|
3886
|
-
return refs;
|
|
3887
|
-
}
|
|
3888
|
-
function inlinePlainText(tokens) {
|
|
3889
|
-
if (!tokens) return "";
|
|
3890
|
-
let out = "";
|
|
3891
|
-
for (const token of tokens) {
|
|
3892
|
-
if (token.type === "image") continue;
|
|
3893
|
-
if (token.type === "html") {
|
|
3894
|
-
out += (token.raw ?? "").replace(HTML_TAG, "");
|
|
3895
|
-
continue;
|
|
3896
|
-
}
|
|
3897
|
-
if (token.type === "br") {
|
|
3898
|
-
out += "\n";
|
|
3899
|
-
continue;
|
|
3900
|
-
}
|
|
3901
|
-
if (token.text && (!token.tokens || token.tokens.length === 0)) {
|
|
3902
|
-
out += token.text;
|
|
3903
|
-
continue;
|
|
3904
|
-
}
|
|
3905
|
-
if (token.tokens?.length) {
|
|
3906
|
-
out += inlinePlainText(token.tokens);
|
|
3907
|
-
continue;
|
|
3908
|
-
}
|
|
3909
|
-
if (token.raw) out += token.raw.replace(HTML_TAG, "");
|
|
3910
|
-
}
|
|
3911
|
-
return out;
|
|
3912
|
-
}
|
|
3913
|
-
function extractImageRefs(token) {
|
|
3914
|
-
const images = [];
|
|
3915
|
-
const collect = (inline2) => {
|
|
3916
|
-
if (inline2.type === "image") {
|
|
3917
|
-
if (inline2.href)
|
|
3918
|
-
images.push({ href: inline2.href, alt: inline2.text ?? "" });
|
|
3919
|
-
return true;
|
|
3920
|
-
}
|
|
3921
|
-
if (inline2.type === "html") {
|
|
3922
|
-
const refs = imagesFromHtml(inline2.raw);
|
|
3923
|
-
if (refs.length > 0) {
|
|
3924
|
-
images.push(...refs);
|
|
3925
|
-
return true;
|
|
3926
|
-
}
|
|
3927
|
-
return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
|
|
3928
|
-
}
|
|
3929
|
-
if (inline2.type === "text" || inline2.type === "escape") {
|
|
3930
|
-
return !(inline2.text ?? "").trim();
|
|
3931
|
-
}
|
|
3932
|
-
return false;
|
|
3933
|
-
};
|
|
3934
|
-
if (token.type === "image" && token.href) {
|
|
3935
|
-
return [{ href: token.href, alt: token.text ?? "" }];
|
|
3936
|
-
}
|
|
3937
|
-
if (token.type === "html") {
|
|
3938
|
-
const refs = imagesFromHtml(token.raw);
|
|
3939
|
-
return refs.length > 0 ? refs : null;
|
|
3940
|
-
}
|
|
3941
|
-
const inline = token.tokens ?? [];
|
|
3942
|
-
if (inline.length === 0) return null;
|
|
3943
|
-
for (const child of inline) {
|
|
3944
|
-
if (!collect(child)) return null;
|
|
3945
|
-
}
|
|
3946
|
-
return images.length > 0 ? images : null;
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
4064
|
// src/markdown-view.ts
|
|
3950
4065
|
var MAX_IMAGE_ROWS = 24;
|
|
3951
4066
|
var RASTER_CACHE_LIMIT = 32;
|
|
@@ -3981,17 +4096,19 @@ function hexToRgb(hex) {
|
|
|
3981
4096
|
function rgba(color) {
|
|
3982
4097
|
return RGBA.fromValues(color[0], color[1], color[2], 255);
|
|
3983
4098
|
}
|
|
3984
|
-
function imageStyledText(image) {
|
|
4099
|
+
function imageStyledText(image, linkUrl) {
|
|
3985
4100
|
const chunks = [];
|
|
3986
4101
|
toImageRuns(image).forEach((runs, rowIndex) => {
|
|
3987
4102
|
if (rowIndex > 0) chunks.push({ __isChunk: true, text: "\n" });
|
|
3988
4103
|
for (const run2 of runs) {
|
|
3989
|
-
|
|
4104
|
+
const chunk = {
|
|
3990
4105
|
__isChunk: true,
|
|
3991
4106
|
text: run2.text,
|
|
3992
4107
|
fg: rgba(run2.top),
|
|
3993
4108
|
bg: rgba(run2.bottom)
|
|
3994
|
-
}
|
|
4109
|
+
};
|
|
4110
|
+
if (linkUrl) chunk.link = { url: linkUrl };
|
|
4111
|
+
chunks.push(chunk);
|
|
3995
4112
|
}
|
|
3996
4113
|
});
|
|
3997
4114
|
return new StyledText3(chunks);
|
|
@@ -4018,15 +4135,21 @@ function createMarkdownView(opts) {
|
|
|
4018
4135
|
tableOptions: { style: "grid" },
|
|
4019
4136
|
width: "100%"
|
|
4020
4137
|
});
|
|
4021
|
-
const caption = (label, color = muted()) => new TextRenderable3(renderer, {
|
|
4022
|
-
content:
|
|
4023
|
-
|
|
4138
|
+
const caption = (label, linkUrl, color = muted()) => new TextRenderable3(renderer, {
|
|
4139
|
+
content: new StyledText3([
|
|
4140
|
+
{
|
|
4141
|
+
__isChunk: true,
|
|
4142
|
+
text: ` ${label}`,
|
|
4143
|
+
fg: RGBA.fromHex(color),
|
|
4144
|
+
...linkUrl ? { link: { url: linkUrl } } : {}
|
|
4145
|
+
}
|
|
4146
|
+
]),
|
|
4024
4147
|
bg: bg(),
|
|
4025
4148
|
wrapMode: "word",
|
|
4026
4149
|
flexShrink: 0,
|
|
4027
4150
|
width: "100%"
|
|
4028
4151
|
});
|
|
4029
|
-
const rasterize = (url) => {
|
|
4152
|
+
const rasterize = (url, declared) => {
|
|
4030
4153
|
const maxCols = Math.max(8, Math.floor(opts.getImageWidth?.() ?? 100));
|
|
4031
4154
|
const key = `${maxCols}|${url}`;
|
|
4032
4155
|
const cached2 = rasterCache.get(key);
|
|
@@ -4036,6 +4159,7 @@ function createMarkdownView(opts) {
|
|
|
4036
4159
|
if (!loader) return null;
|
|
4037
4160
|
const bytes = await loader(url);
|
|
4038
4161
|
if (!bytes) return null;
|
|
4162
|
+
if (!await shouldRenderImage(bytes, declared)) return null;
|
|
4039
4163
|
return rasterizeImage(bytes, {
|
|
4040
4164
|
maxCols,
|
|
4041
4165
|
maxRows: maxImageRows,
|
|
@@ -4053,24 +4177,25 @@ function createMarkdownView(opts) {
|
|
|
4053
4177
|
const imageNode = (image) => {
|
|
4054
4178
|
const label = `\u{1F5BC} ${image.alt.trim() || "image"}`;
|
|
4055
4179
|
const url = imageBase ? resolveReadmeImageUrl(image.href, imageBase) : null;
|
|
4056
|
-
if (!imagesEnabled || !url || !opts.loadImage)
|
|
4180
|
+
if (!imagesEnabled || !url || !opts.loadImage)
|
|
4181
|
+
return caption(label, image.linkUrl);
|
|
4057
4182
|
const container = new BoxRenderable3(renderer, {
|
|
4058
4183
|
flexDirection: "column",
|
|
4059
4184
|
flexShrink: 0,
|
|
4060
4185
|
width: "100%"
|
|
4061
4186
|
});
|
|
4062
|
-
const placeholder = caption(`${label} (loading\u2026)
|
|
4187
|
+
const placeholder = caption(`${label} (loading\u2026)`, image.linkUrl);
|
|
4063
4188
|
container.add(placeholder);
|
|
4064
|
-
void rasterize(url).then(
|
|
4189
|
+
void rasterize(url, image.declared).then(
|
|
4065
4190
|
(cells) => {
|
|
4066
4191
|
if (container.isDestroyed || !container.parent) return;
|
|
4067
4192
|
container.remove(placeholder);
|
|
4068
4193
|
placeholder.destroy();
|
|
4069
|
-
container.add(caption(label));
|
|
4194
|
+
container.add(caption(label, image.linkUrl));
|
|
4070
4195
|
if (cells) {
|
|
4071
4196
|
container.add(
|
|
4072
4197
|
new TextRenderable3(renderer, {
|
|
4073
|
-
content: imageStyledText(cells),
|
|
4198
|
+
content: imageStyledText(cells, image.linkUrl),
|
|
4074
4199
|
bg: bg(),
|
|
4075
4200
|
wrapMode: "none",
|
|
4076
4201
|
selectable: false,
|
|
@@ -4112,17 +4237,22 @@ function createMarkdownView(opts) {
|
|
|
4112
4237
|
const buildRenderNode = () => (token, _context) => {
|
|
4113
4238
|
if (token.type === "heading") return headingNode(token);
|
|
4114
4239
|
const images = extractImageRefs(token);
|
|
4115
|
-
if (images
|
|
4116
|
-
|
|
4117
|
-
|
|
4240
|
+
if (images === null) return void 0;
|
|
4241
|
+
if (images.length === 0) {
|
|
4242
|
+
return new BoxRenderable3(renderer, {
|
|
4118
4243
|
flexShrink: 0,
|
|
4119
4244
|
width: "100%",
|
|
4120
|
-
|
|
4245
|
+
height: 0
|
|
4121
4246
|
});
|
|
4122
|
-
for (const image of images) box.add(imageNode(image));
|
|
4123
|
-
return box;
|
|
4124
4247
|
}
|
|
4125
|
-
|
|
4248
|
+
const box = new BoxRenderable3(renderer, {
|
|
4249
|
+
flexDirection: "column",
|
|
4250
|
+
flexShrink: 0,
|
|
4251
|
+
width: "100%",
|
|
4252
|
+
marginTop: 1
|
|
4253
|
+
});
|
|
4254
|
+
for (const image of images) box.add(imageNode(image));
|
|
4255
|
+
return box;
|
|
4126
4256
|
};
|
|
4127
4257
|
renderable.renderNode = buildRenderNode();
|
|
4128
4258
|
return {
|
|
@@ -4572,7 +4702,7 @@ async function launchBrowser(theme_override) {
|
|
|
4572
4702
|
const root = renderer.root;
|
|
4573
4703
|
root.flexDirection = "column";
|
|
4574
4704
|
const config = loadConfig();
|
|
4575
|
-
|
|
4705
|
+
let githubToken = config.githubToken || process.env.GITHUB_TOKEN;
|
|
4576
4706
|
const theme = loadTheme(theme_override || config.theme);
|
|
4577
4707
|
Object.assign(colors, theme);
|
|
4578
4708
|
if (terminalBg) {
|
|
@@ -4755,6 +4885,10 @@ async function launchBrowser(theme_override) {
|
|
|
4755
4885
|
currentMode = "search";
|
|
4756
4886
|
showLanding(false);
|
|
4757
4887
|
showSearchMode();
|
|
4888
|
+
if (currentQueryInput) {
|
|
4889
|
+
searchInput.value = currentQueryInput;
|
|
4890
|
+
renderer.requestRender();
|
|
4891
|
+
}
|
|
4758
4892
|
}
|
|
4759
4893
|
},
|
|
4760
4894
|
{
|
|
@@ -6079,9 +6213,85 @@ async function launchBrowser(theme_override) {
|
|
|
6079
6213
|
setStatus(nextTip());
|
|
6080
6214
|
}
|
|
6081
6215
|
}, 2e3);
|
|
6216
|
+
const tokenPromptBox = new BoxRenderable5(renderer, {
|
|
6217
|
+
visible: false,
|
|
6218
|
+
position: "absolute",
|
|
6219
|
+
top: 0,
|
|
6220
|
+
left: 0,
|
|
6221
|
+
width: "100%",
|
|
6222
|
+
flexDirection: "row",
|
|
6223
|
+
paddingY: 1,
|
|
6224
|
+
paddingLeft: 1,
|
|
6225
|
+
backgroundColor: colors.surface
|
|
6226
|
+
});
|
|
6227
|
+
const tokenPromptLabel = new TextRenderable5(renderer, {
|
|
6228
|
+
content: " GitHub token:",
|
|
6229
|
+
fg: colors.accent,
|
|
6230
|
+
height: 1
|
|
6231
|
+
});
|
|
6232
|
+
const tokenPromptInput = new InputRenderable(renderer, {
|
|
6233
|
+
placeholder: "paste token, Enter to save \u2014 Esc cancels",
|
|
6234
|
+
value: "",
|
|
6235
|
+
backgroundColor: colors.surface,
|
|
6236
|
+
textColor: colors.text,
|
|
6237
|
+
flexGrow: 1
|
|
6238
|
+
});
|
|
6239
|
+
tokenPromptBox.add(tokenPromptLabel);
|
|
6240
|
+
tokenPromptBox.add(tokenPromptInput);
|
|
6241
|
+
root.add(tokenPromptBox);
|
|
6242
|
+
let tokenPromptQuery = null;
|
|
6243
|
+
function showTokenPrompt(query) {
|
|
6244
|
+
tokenPromptQuery = query;
|
|
6245
|
+
tokenPromptInput.value = "";
|
|
6246
|
+
tokenPromptBox.visible = true;
|
|
6247
|
+
searchBox.visible = false;
|
|
6248
|
+
tokenPromptInput.focus();
|
|
6249
|
+
renderer.requestRender();
|
|
6250
|
+
}
|
|
6251
|
+
function hideTokenPrompt() {
|
|
6252
|
+
tokenPromptBox.visible = false;
|
|
6253
|
+
tokenPromptQuery = null;
|
|
6254
|
+
if (currentMode === "search" || currentMode === "packages") {
|
|
6255
|
+
searchBox.visible = true;
|
|
6256
|
+
searchInput.focus();
|
|
6257
|
+
}
|
|
6258
|
+
renderer.requestRender();
|
|
6259
|
+
}
|
|
6260
|
+
function persistToken2(token) {
|
|
6261
|
+
githubToken = token;
|
|
6262
|
+
config.githubToken = token;
|
|
6263
|
+
saveConfig(config);
|
|
6264
|
+
}
|
|
6265
|
+
tokenPromptInput.on("enter", () => {
|
|
6266
|
+
const token = tokenPromptInput.value.trim();
|
|
6267
|
+
const query = tokenPromptQuery;
|
|
6268
|
+
hideTokenPrompt();
|
|
6269
|
+
if (!token || !query) return;
|
|
6270
|
+
persistToken2(token);
|
|
6271
|
+
setStatus("Token saved");
|
|
6272
|
+
doSearch(query);
|
|
6273
|
+
});
|
|
6274
|
+
function fixTokenAction() {
|
|
6275
|
+
showTokenPrompt(currentQueryInput || "stars:>10000");
|
|
6276
|
+
}
|
|
6277
|
+
function unsetTokenAction() {
|
|
6278
|
+
githubToken = void 0;
|
|
6279
|
+
if (config.githubToken) {
|
|
6280
|
+
delete config.githubToken;
|
|
6281
|
+
saveConfig(config);
|
|
6282
|
+
}
|
|
6283
|
+
setStatus("Token cleared \u2014 searching without one");
|
|
6284
|
+
if (currentQueryInput) doSearch(currentQueryInput);
|
|
6285
|
+
}
|
|
6082
6286
|
function setStatus(msg) {
|
|
6083
6287
|
statusMgr.set("idle", msg);
|
|
6084
6288
|
}
|
|
6289
|
+
let errorHint = null;
|
|
6290
|
+
function reportError2(err) {
|
|
6291
|
+
errorHint = err instanceof AuthError ? "auth" : err instanceof ForbiddenError ? "forbidden" : err instanceof RateLimitError ? "ratelimit" : err instanceof NetworkError ? "network" : null;
|
|
6292
|
+
const msg = err instanceof SearchCliError ? err.userMessage : err instanceof Error ? err.message : String(err);
|
|
6293
|
+
statusMgr.set("error", msg.slice(0, 60));
|
|
6294
|
+
}
|
|
6085
6295
|
function buildSessionState() {
|
|
6086
6296
|
const mode = currentMode === "trending" ? "trending" : currentMode === "packages" ? "packages" : "search";
|
|
6087
6297
|
return {
|
|
@@ -6298,6 +6508,7 @@ async function launchBrowser(theme_override) {
|
|
|
6298
6508
|
async function loadTrending() {
|
|
6299
6509
|
currentMode = "trending";
|
|
6300
6510
|
isLoading = true;
|
|
6511
|
+
errorHint = null;
|
|
6301
6512
|
searchBox.visible = false;
|
|
6302
6513
|
toolbarText.visible = false;
|
|
6303
6514
|
trendingTabBox.visible = true;
|
|
@@ -6347,11 +6558,10 @@ async function launchBrowser(theme_override) {
|
|
|
6347
6558
|
resultCount: fetched.length
|
|
6348
6559
|
});
|
|
6349
6560
|
} catch (err) {
|
|
6350
|
-
const msg = err instanceof SearchCliError ? err.userMessage : err instanceof Error ? err.message : String(err);
|
|
6351
6561
|
resultsSelect.options = [
|
|
6352
6562
|
{ name: " (error)", description: "", value: null }
|
|
6353
6563
|
];
|
|
6354
|
-
|
|
6564
|
+
reportError2(err);
|
|
6355
6565
|
}
|
|
6356
6566
|
isLoading = false;
|
|
6357
6567
|
renderer.requestRender();
|
|
@@ -6390,6 +6600,7 @@ async function launchBrowser(theme_override) {
|
|
|
6390
6600
|
if (q === "" || isLoading) return;
|
|
6391
6601
|
currentQueryInput = q;
|
|
6392
6602
|
isLoading = true;
|
|
6603
|
+
errorHint = null;
|
|
6393
6604
|
resultsSelect.options = [
|
|
6394
6605
|
{ name: " Searching packages...", description: "", value: null }
|
|
6395
6606
|
];
|
|
@@ -6427,6 +6638,7 @@ async function launchBrowser(theme_override) {
|
|
|
6427
6638
|
async function doSearch(queryText, append = false) {
|
|
6428
6639
|
const q = queryText.trim();
|
|
6429
6640
|
if (q === "" || isLoading) return;
|
|
6641
|
+
errorHint = null;
|
|
6430
6642
|
if (!append) currentPage = 1;
|
|
6431
6643
|
deepDiveActive = false;
|
|
6432
6644
|
isLoading = true;
|
|
@@ -6489,8 +6701,7 @@ async function launchBrowser(theme_override) {
|
|
|
6489
6701
|
{ name: " (error)", description: "", value: null }
|
|
6490
6702
|
];
|
|
6491
6703
|
}
|
|
6492
|
-
|
|
6493
|
-
statusMgr.set("error", msg.slice(0, 60));
|
|
6704
|
+
reportError2(err);
|
|
6494
6705
|
}
|
|
6495
6706
|
isLoading = false;
|
|
6496
6707
|
renderer.requestRender();
|
|
@@ -6587,6 +6798,16 @@ ${pack.description ?? ""}`;
|
|
|
6587
6798
|
}
|
|
6588
6799
|
});
|
|
6589
6800
|
renderer.keyInput.on("keypress", (key) => {
|
|
6801
|
+
if (tokenPromptBox.visible) {
|
|
6802
|
+
if (key.name === "escape") {
|
|
6803
|
+
hideTokenPrompt();
|
|
6804
|
+
setStatus("Token entry cancelled");
|
|
6805
|
+
renderer.requestRender();
|
|
6806
|
+
return;
|
|
6807
|
+
}
|
|
6808
|
+
if (key.name === "enter" || key.name === "return") return;
|
|
6809
|
+
return;
|
|
6810
|
+
}
|
|
6590
6811
|
if (currentOverlay !== "none") {
|
|
6591
6812
|
if (key.name === "escape") {
|
|
6592
6813
|
if (currentOverlay === "leader") {
|
|
@@ -6890,6 +7111,27 @@ ${pack.description ?? ""}`;
|
|
|
6890
7111
|
showLeaderMenu();
|
|
6891
7112
|
return;
|
|
6892
7113
|
}
|
|
7114
|
+
if (!searchInput.focused && errorHint) {
|
|
7115
|
+
if (key.name === "r") {
|
|
7116
|
+
errorHint = null;
|
|
7117
|
+
if (currentMode === "trending") loadTrending();
|
|
7118
|
+
else if (currentQueryInput) doSearch(currentQueryInput);
|
|
7119
|
+
renderer.requestRender();
|
|
7120
|
+
return;
|
|
7121
|
+
}
|
|
7122
|
+
if (key.name === "t" && (errorHint === "auth" || errorHint === "forbidden")) {
|
|
7123
|
+
fixTokenAction();
|
|
7124
|
+
return;
|
|
7125
|
+
}
|
|
7126
|
+
if (key.name === "u" && errorHint === "auth") {
|
|
7127
|
+
unsetTokenAction();
|
|
7128
|
+
return;
|
|
7129
|
+
}
|
|
7130
|
+
if (key.name === "c" && errorHint === "ratelimit") {
|
|
7131
|
+
fixTokenAction();
|
|
7132
|
+
return;
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
6893
7135
|
if (key.name === "t" && !searchInput.focused) {
|
|
6894
7136
|
const current = config.theme;
|
|
6895
7137
|
const themes = listThemes();
|
|
@@ -6984,25 +7226,10 @@ ${pack.description ?? ""}`;
|
|
|
6984
7226
|
}
|
|
6985
7227
|
});
|
|
6986
7228
|
const _currentVersion = getVersion();
|
|
6987
|
-
|
|
6988
|
-
showLanding(true);
|
|
6989
|
-
}
|
|
7229
|
+
showLanding(true);
|
|
6990
7230
|
checkForUpdateAndShow();
|
|
6991
7231
|
checkPostUpgradePanel();
|
|
6992
7232
|
renderer.start();
|
|
6993
|
-
if (session?.mode) {
|
|
6994
|
-
if (currentMode === "trending") {
|
|
6995
|
-
loadTrending();
|
|
6996
|
-
} else {
|
|
6997
|
-
searchInput.value = currentQueryInput;
|
|
6998
|
-
if (currentQueryInput) {
|
|
6999
|
-
doSearch(currentQueryInput);
|
|
7000
|
-
} else {
|
|
7001
|
-
showSearchMode();
|
|
7002
|
-
}
|
|
7003
|
-
searchInput.focus();
|
|
7004
|
-
}
|
|
7005
|
-
}
|
|
7006
7233
|
renderer.requestRender();
|
|
7007
7234
|
}
|
|
7008
7235
|
function formatResultLine(repo) {
|
|
@@ -7490,7 +7717,18 @@ var TOOLS = [
|
|
|
7490
7717
|
verbose: false,
|
|
7491
7718
|
token
|
|
7492
7719
|
};
|
|
7493
|
-
|
|
7720
|
+
let response;
|
|
7721
|
+
try {
|
|
7722
|
+
response = await provider.search(parsed, options);
|
|
7723
|
+
} catch (err) {
|
|
7724
|
+
if (err instanceof RateLimitError) {
|
|
7725
|
+
return {
|
|
7726
|
+
text: `GitHub API rate limit exceeded for "${query}". Set a token (GITHUB_TOKEN or the tool's token argument) to raise the limit from 60/hr to 5,000/hr.`,
|
|
7727
|
+
data: { totalCount: 0, repos: [], rateLimited: true }
|
|
7728
|
+
};
|
|
7729
|
+
}
|
|
7730
|
+
throw err;
|
|
7731
|
+
}
|
|
7494
7732
|
const repos = rankRepos(response.repos, sort);
|
|
7495
7733
|
try {
|
|
7496
7734
|
appendHistory({
|