github-search-cli 9.7.0 → 9.8.0
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 +63 -0
- package/README.md +17 -16
- package/bin/ghfind.js +5 -5
- package/dist/cli.js +1068 -342
- package/package.json +9 -3
- package/release-notes/9.7.1.md +62 -0
- package/release-notes/9.8.0.md +55 -0
- package/scripts/build.ts +1 -1
- package/scripts/postinstall.js +8 -8
- package/src/cli.ts +21 -13
- package/src/compare.ts +5 -5
- package/src/config.ts +3 -3
- package/src/deepdive.ts +0 -1
- package/src/doctor.ts +144 -8
- package/src/error-report.ts +14 -8
- package/src/export.ts +1 -1
- package/src/help.ts +2 -1
- package/src/history.ts +5 -5
- package/src/image-loader.ts +248 -0
- package/src/image-render.ts +209 -0
- package/src/init.ts +2 -2
- package/src/login.ts +4 -4
- package/src/markdown-images.ts +123 -0
- package/src/markdown-view.ts +300 -0
- package/src/mcp-server.ts +4 -3
- package/src/open-url.ts +16 -5
- package/src/output.ts +1 -1
- package/src/package-browser.ts +1 -1
- package/src/png.ts +260 -0
- package/src/releases.ts +1 -14
- package/src/search-state.ts +1 -4
- package/src/search.ts +3 -4
- package/src/share.ts +2 -2
- package/src/storage.ts +4 -4
- package/src/themes.ts +1 -0
- package/src/trending-parser.ts +3 -4
- package/src/trending.ts +7 -7
- package/src/tui.ts +115 -83
- package/src/update-check.ts +96 -34
- package/src/version.ts +3 -3
- package/src/watch.ts +1 -1
- package/src/markdown-render.ts +0 -211
package/dist/cli.js
CHANGED
|
@@ -62,14 +62,14 @@ function flattenHtml(html) {
|
|
|
62
62
|
}
|
|
63
63
|
function parseRepoLink(block) {
|
|
64
64
|
const anchor = block.match(
|
|
65
|
-
/<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([
|
|
65
|
+
/<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([^"?#]+)"[^>]*class="Link"/
|
|
66
66
|
);
|
|
67
67
|
if (!anchor) return void 0;
|
|
68
68
|
return { owner: anchor[1], name: anchor[2] };
|
|
69
69
|
}
|
|
70
70
|
function parseStars(block) {
|
|
71
71
|
const starsMatch = block.match(
|
|
72
|
-
/href="\/[
|
|
72
|
+
/href="\/[^/"]+\/[^/"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/
|
|
73
73
|
);
|
|
74
74
|
return starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
|
|
75
75
|
}
|
|
@@ -91,9 +91,8 @@ function parseTrendingHtml(html) {
|
|
|
91
91
|
const flat = flattenHtml(html);
|
|
92
92
|
const repos = [];
|
|
93
93
|
const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
|
|
94
|
-
let match;
|
|
95
94
|
let rank = 0;
|
|
96
|
-
|
|
95
|
+
for (const match of flat.matchAll(articleRe)) {
|
|
97
96
|
rank++;
|
|
98
97
|
const block = match[1];
|
|
99
98
|
const link = parseRepoLink(block);
|
|
@@ -240,7 +239,7 @@ function suggestFor(input) {
|
|
|
240
239
|
return QUALIFIER_SUGGESTIONS[trimmed];
|
|
241
240
|
}
|
|
242
241
|
const matches = KNOWN_QUALIFIERS.filter((k) => k.startsWith(trimmed));
|
|
243
|
-
return matches.map((k) => k
|
|
242
|
+
return matches.map((k) => `${k}:`);
|
|
244
243
|
}
|
|
245
244
|
function tokenize(input) {
|
|
246
245
|
const tokens = [];
|
|
@@ -353,7 +352,6 @@ function githubSortParam(sort) {
|
|
|
353
352
|
return { sort: "forks", order: "desc" };
|
|
354
353
|
case "updated":
|
|
355
354
|
return { sort: "updated", order: "desc" };
|
|
356
|
-
case "best-match":
|
|
357
355
|
default:
|
|
358
356
|
return {};
|
|
359
357
|
}
|
|
@@ -378,7 +376,6 @@ function rankRepos(repos, strategy) {
|
|
|
378
376
|
case "updated":
|
|
379
377
|
copy.sort(byKey((r) => Date.parse(r.updatedAt)));
|
|
380
378
|
break;
|
|
381
|
-
case "best-match":
|
|
382
379
|
default:
|
|
383
380
|
break;
|
|
384
381
|
}
|
|
@@ -585,6 +582,7 @@ function trendingRepoToRepo(r) {
|
|
|
585
582
|
}
|
|
586
583
|
var TrendingAdapter = class {
|
|
587
584
|
name = "trending";
|
|
585
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: part of the adapter constructor contract — callers pass a shared logger (search.ts:757) even though this adapter currently logs nothing itself.
|
|
588
586
|
logger;
|
|
589
587
|
constructor(logger2 = noopLogger) {
|
|
590
588
|
this.logger = logger2;
|
|
@@ -740,12 +738,17 @@ import {
|
|
|
740
738
|
} from "@opentui/core";
|
|
741
739
|
|
|
742
740
|
// src/open-url.ts
|
|
741
|
+
import { spawn } from "child_process";
|
|
743
742
|
function openUrl(url) {
|
|
744
743
|
try {
|
|
745
744
|
const p = process.platform;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
745
|
+
const cmd = p === "win32" ? "explorer" : p === "darwin" ? "open" : "xdg-open";
|
|
746
|
+
if (typeof Bun !== "undefined") {
|
|
747
|
+
Bun.spawn([cmd, url]);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const child = spawn(cmd, [url], { detached: true, stdio: "ignore" });
|
|
751
|
+
child.unref();
|
|
749
752
|
} catch {
|
|
750
753
|
}
|
|
751
754
|
}
|
|
@@ -935,7 +938,7 @@ function formatPackageLine(pack) {
|
|
|
935
938
|
const nameStr = `${pack.name}@${pack.version}`.padEnd(28).slice(0, 28);
|
|
936
939
|
const dlStr = `\u2193 ${fmtCount(pack.downloads)}`.padEnd(20).slice(0, 20);
|
|
937
940
|
const scoreStr = `score ${pack.score.toFixed(2)}`.padEnd(12).slice(0, 12);
|
|
938
|
-
const desc = (pack.description ?? "").length > 60 ? (pack.description ?? "").slice(0, 57)
|
|
941
|
+
const desc = (pack.description ?? "").length > 60 ? `${(pack.description ?? "").slice(0, 57)}...` : pack.description ?? "";
|
|
939
942
|
const line1 = t`${bold(fg(C.nameText)(nameStr))}${fg(C.cyan)(dlStr)}${fg(C.gold)(scoreStr)}`;
|
|
940
943
|
const line2 = t`${dim(fg(C.descText)(` ${desc}`))}`;
|
|
941
944
|
const newline = { text: "\n", __isChunk: true };
|
|
@@ -1198,9 +1201,13 @@ var results = [];
|
|
|
1198
1201
|
function add(status, label, detail) {
|
|
1199
1202
|
results.push({ status, label, detail });
|
|
1200
1203
|
}
|
|
1201
|
-
function run(cmd, args) {
|
|
1204
|
+
function run(cmd, args, opts = {}) {
|
|
1202
1205
|
try {
|
|
1203
|
-
const res = spawnSync(cmd, args, {
|
|
1206
|
+
const res = spawnSync(cmd, args, {
|
|
1207
|
+
encoding: "utf8",
|
|
1208
|
+
timeout: 15e3,
|
|
1209
|
+
...opts
|
|
1210
|
+
});
|
|
1204
1211
|
return {
|
|
1205
1212
|
status: res.status,
|
|
1206
1213
|
stdout: res.stdout ?? "",
|
|
@@ -1375,6 +1382,97 @@ function checkDirs() {
|
|
|
1375
1382
|
}
|
|
1376
1383
|
}
|
|
1377
1384
|
}
|
|
1385
|
+
var PKG_NAME = "github-search-cli";
|
|
1386
|
+
var BIN_NAME = "ghfind";
|
|
1387
|
+
function runningVersion() {
|
|
1388
|
+
try {
|
|
1389
|
+
const pkg = JSON.parse(
|
|
1390
|
+
readFileSync(join(PKG_DIR, "package.json"), "utf8")
|
|
1391
|
+
);
|
|
1392
|
+
return pkg.version ?? "";
|
|
1393
|
+
} catch {
|
|
1394
|
+
return "";
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function installVersionAt(shimPath) {
|
|
1398
|
+
const dir = dirname(shimPath);
|
|
1399
|
+
for (const ancestor of [dir, dirname(dir)]) {
|
|
1400
|
+
for (const sub of ["node_modules", join("lib", "node_modules")]) {
|
|
1401
|
+
const pkgJson = join(ancestor, sub, PKG_NAME, "package.json");
|
|
1402
|
+
if (existsSync(pkgJson)) {
|
|
1403
|
+
try {
|
|
1404
|
+
const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
|
|
1405
|
+
return { version: pkg.version ?? "", dir: dirname(pkgJson) };
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
const res = run(shimPath, ["--version"], {
|
|
1412
|
+
shell: platform() === "win32",
|
|
1413
|
+
timeout: 1e4
|
|
1414
|
+
});
|
|
1415
|
+
const m = /\d+\.\d+\.\d+/.exec(res.stdout);
|
|
1416
|
+
return m ? { version: m[0], dir } : null;
|
|
1417
|
+
}
|
|
1418
|
+
function checkInstallShadowing() {
|
|
1419
|
+
const isWin = platform() === "win32";
|
|
1420
|
+
const res = isWin ? run("where", [BIN_NAME]) : run("which", ["-a", BIN_NAME]);
|
|
1421
|
+
const shims = [
|
|
1422
|
+
...new Set(
|
|
1423
|
+
res.stdout.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0)
|
|
1424
|
+
)
|
|
1425
|
+
];
|
|
1426
|
+
if (shims.length === 0) {
|
|
1427
|
+
add("WARN", "Global installs", `${BIN_NAME} not found on PATH.`);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const running = runningVersion();
|
|
1431
|
+
const norm = (p) => isWin ? p.replace(/\\/g, "/").toLowerCase() : p;
|
|
1432
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
1433
|
+
const stale = [];
|
|
1434
|
+
let knownCount = 0;
|
|
1435
|
+
let staleVersion = "";
|
|
1436
|
+
let staleDir = "";
|
|
1437
|
+
for (const shim of shims) {
|
|
1438
|
+
const info = installVersionAt(shim);
|
|
1439
|
+
const dirKey = norm(dirname(shim));
|
|
1440
|
+
if (seenDirs.has(dirKey)) continue;
|
|
1441
|
+
seenDirs.add(dirKey);
|
|
1442
|
+
knownCount++;
|
|
1443
|
+
const isRunningInstall = info !== null && norm(info.dir) === norm(PKG_DIR);
|
|
1444
|
+
if (!isRunningInstall && info !== null && running && info.version !== running) {
|
|
1445
|
+
stale.push(`${dirname(shim)} (v${info.version} \u2260 running v${running})`);
|
|
1446
|
+
staleVersion = info.version;
|
|
1447
|
+
staleDir = dirname(shim);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (stale.length > 0 && knownCount > 1) {
|
|
1451
|
+
add(
|
|
1452
|
+
"WARN",
|
|
1453
|
+
"Global installs",
|
|
1454
|
+
`${knownCount} install(s) on PATH; stale copies shadowing updates \u2192 ${stale.join("; ")}. Remove with: npm uninstall -g ${PKG_NAME} --prefix <dir>`
|
|
1455
|
+
);
|
|
1456
|
+
} else if (stale.length > 0) {
|
|
1457
|
+
add(
|
|
1458
|
+
"PASS",
|
|
1459
|
+
"Global installs",
|
|
1460
|
+
`v${staleVersion} at ${staleDir} (running v${running || "?"} from source/build) \u2014 single install, nothing shadowed.`
|
|
1461
|
+
);
|
|
1462
|
+
} else if (knownCount > 1) {
|
|
1463
|
+
add(
|
|
1464
|
+
"PASS",
|
|
1465
|
+
"Global installs",
|
|
1466
|
+
`${knownCount} install(s) on PATH, all v${running || "?"}.`
|
|
1467
|
+
);
|
|
1468
|
+
} else {
|
|
1469
|
+
add(
|
|
1470
|
+
"PASS",
|
|
1471
|
+
"Global installs",
|
|
1472
|
+
`v${running || "?"} at ${dirname(shims[0])}.`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1378
1476
|
function printReport() {
|
|
1379
1477
|
const noColor = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
1380
1478
|
const colors2 = (s, n) => {
|
|
@@ -1417,6 +1515,7 @@ async function runDoctor() {
|
|
|
1417
1515
|
checkConfig();
|
|
1418
1516
|
checkGithubToken();
|
|
1419
1517
|
checkDirs();
|
|
1518
|
+
checkInstallShadowing();
|
|
1420
1519
|
printReport();
|
|
1421
1520
|
const hasFail = results.some((r) => r.status === "FAIL");
|
|
1422
1521
|
process.exitCode = hasFail ? 1 : 0;
|
|
@@ -1429,7 +1528,7 @@ import {
|
|
|
1429
1528
|
version as nodeVersion,
|
|
1430
1529
|
versions as nodeVersions
|
|
1431
1530
|
} from "process";
|
|
1432
|
-
import { spawn } from "child_process";
|
|
1531
|
+
import { spawn as spawn2 } from "child_process";
|
|
1433
1532
|
|
|
1434
1533
|
// src/version.ts
|
|
1435
1534
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -1439,7 +1538,7 @@ var cached = null;
|
|
|
1439
1538
|
function getVersion() {
|
|
1440
1539
|
if (cached) return cached;
|
|
1441
1540
|
if (true) {
|
|
1442
|
-
cached = "9.
|
|
1541
|
+
cached = "9.8.0";
|
|
1443
1542
|
return cached;
|
|
1444
1543
|
}
|
|
1445
1544
|
try {
|
|
@@ -1515,14 +1614,15 @@ function tryOpenUrl(url) {
|
|
|
1515
1614
|
try {
|
|
1516
1615
|
const p = osPlatform;
|
|
1517
1616
|
if (typeof Bun !== "undefined") {
|
|
1518
|
-
if (p === "win32")
|
|
1519
|
-
|
|
1617
|
+
if (p === "win32") {
|
|
1618
|
+
Bun.spawn(["explorer", url]);
|
|
1619
|
+
} else if (p === "darwin") Bun.spawn(["open", url]);
|
|
1520
1620
|
else Bun.spawn(["xdg-open", url]);
|
|
1521
1621
|
return true;
|
|
1522
1622
|
}
|
|
1523
|
-
const cmd = p === "win32" ? "
|
|
1524
|
-
const args =
|
|
1525
|
-
const child =
|
|
1623
|
+
const cmd = p === "win32" ? "explorer" : p === "darwin" ? "open" : "xdg-open";
|
|
1624
|
+
const args = [url];
|
|
1625
|
+
const child = spawn2(cmd, args, { detached: true, stdio: "ignore" });
|
|
1526
1626
|
child.unref();
|
|
1527
1627
|
return true;
|
|
1528
1628
|
} catch {
|
|
@@ -1543,11 +1643,11 @@ function reportError(err, context = {
|
|
|
1543
1643
|
console.error("");
|
|
1544
1644
|
const opened = tryOpenUrl(url);
|
|
1545
1645
|
if (opened) {
|
|
1546
|
-
console.error(
|
|
1646
|
+
console.error(` \u2192 Opened: ${url}`);
|
|
1547
1647
|
} else {
|
|
1548
1648
|
console.error(" Copy this URL to file a bug report:");
|
|
1549
1649
|
console.error("");
|
|
1550
|
-
console.error(
|
|
1650
|
+
console.error(` ${url}`);
|
|
1551
1651
|
}
|
|
1552
1652
|
console.error("");
|
|
1553
1653
|
return url;
|
|
@@ -2148,7 +2248,8 @@ function appendJSONL(filename, obj) {
|
|
|
2148
2248
|
try {
|
|
2149
2249
|
appendFileSync(
|
|
2150
2250
|
join3(stateDir(), filename),
|
|
2151
|
-
JSON.stringify(obj)
|
|
2251
|
+
`${JSON.stringify(obj)}
|
|
2252
|
+
`,
|
|
2152
2253
|
"utf-8"
|
|
2153
2254
|
);
|
|
2154
2255
|
} catch {
|
|
@@ -2281,8 +2382,8 @@ var PER_REPO = 5;
|
|
|
2281
2382
|
function loadCache() {
|
|
2282
2383
|
return readJSON(RELEASES_FILE, {});
|
|
2283
2384
|
}
|
|
2284
|
-
function saveCache(
|
|
2285
|
-
writeJSON(RELEASES_FILE,
|
|
2385
|
+
function saveCache(cache2) {
|
|
2386
|
+
writeJSON(RELEASES_FILE, cache2);
|
|
2286
2387
|
}
|
|
2287
2388
|
function toTracked(raw) {
|
|
2288
2389
|
return raw.filter((r) => r.draft !== true).map((r) => ({
|
|
@@ -2299,7 +2400,7 @@ async function fetchReleases(fullName, etag, token) {
|
|
|
2299
2400
|
Accept: "application/vnd.github+json"
|
|
2300
2401
|
};
|
|
2301
2402
|
if (etag) headers["If-None-Match"] = etag;
|
|
2302
|
-
if (token) headers
|
|
2403
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2303
2404
|
const res = await fetch(
|
|
2304
2405
|
`https://api.github.com/repos/${fullName}/releases?per_page=${PER_REPO}`,
|
|
2305
2406
|
{ headers }
|
|
@@ -2313,13 +2414,13 @@ async function fetchReleases(fullName, etag, token) {
|
|
|
2313
2414
|
}
|
|
2314
2415
|
async function checkReleases(opts = {}) {
|
|
2315
2416
|
const bookmarks = getBookmarks();
|
|
2316
|
-
const
|
|
2417
|
+
const cache2 = loadCache();
|
|
2317
2418
|
const config = loadConfig();
|
|
2318
2419
|
const token = opts.token ?? config.githubToken ?? process.env.GITHUB_TOKEN;
|
|
2319
2420
|
const results2 = [];
|
|
2320
2421
|
for (const bm of bookmarks) {
|
|
2321
2422
|
try {
|
|
2322
|
-
const prev =
|
|
2423
|
+
const prev = cache2[bm.repo.fullName];
|
|
2323
2424
|
const lastSeen = getLastSeen(bm.repo.fullName);
|
|
2324
2425
|
const isNew = (r) => !r.publishedAt || new Date(r.publishedAt).getTime() > lastSeen;
|
|
2325
2426
|
const { status, etag, releases } = await fetchReleases(
|
|
@@ -2342,7 +2443,7 @@ async function checkReleases(opts = {}) {
|
|
|
2342
2443
|
});
|
|
2343
2444
|
continue;
|
|
2344
2445
|
}
|
|
2345
|
-
|
|
2446
|
+
cache2[bm.repo.fullName] = { checkedAt: Date.now(), etag, releases };
|
|
2346
2447
|
results2.push({
|
|
2347
2448
|
fullName: bm.repo.fullName,
|
|
2348
2449
|
newReleases: releases.filter(isNew)
|
|
@@ -2355,7 +2456,7 @@ async function checkReleases(opts = {}) {
|
|
|
2355
2456
|
});
|
|
2356
2457
|
}
|
|
2357
2458
|
}
|
|
2358
|
-
saveCache(
|
|
2459
|
+
saveCache(cache2);
|
|
2359
2460
|
return results2;
|
|
2360
2461
|
}
|
|
2361
2462
|
async function checkAndNotify(opts = {}) {
|
|
@@ -2372,9 +2473,9 @@ async function checkAndNotify(opts = {}) {
|
|
|
2372
2473
|
return results2;
|
|
2373
2474
|
}
|
|
2374
2475
|
function allCachedReleases() {
|
|
2375
|
-
const
|
|
2476
|
+
const cache2 = loadCache();
|
|
2376
2477
|
const bookmarks = getBookmarks();
|
|
2377
|
-
return Object.entries(
|
|
2478
|
+
return Object.entries(cache2).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
|
|
2378
2479
|
(a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
|
|
2379
2480
|
);
|
|
2380
2481
|
}
|
|
@@ -2536,7 +2637,7 @@ async function runInitWizard() {
|
|
|
2536
2637
|
const themeStr = await ask(` Theme [${themes.join("/")}] (tokyo-night): `);
|
|
2537
2638
|
const config = {
|
|
2538
2639
|
defaultSort: ["best-match", "stars", "updated", "forks"].includes(sortStr) ? sortStr : "best-match",
|
|
2539
|
-
defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr) || 50)),
|
|
2640
|
+
defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr, 10) || 50)),
|
|
2540
2641
|
theme: themes.includes(themeStr) ? themeStr : themes[0],
|
|
2541
2642
|
cacheTtlSeconds: 300,
|
|
2542
2643
|
defaultTab: "search"
|
|
@@ -2585,18 +2686,18 @@ function buildComparisonTable(repos) {
|
|
|
2585
2686
|
const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
|
|
2586
2687
|
const sep = ["".padEnd(14, "\u2500"), ...names.map(() => "".padEnd(colW, "\u2500"))];
|
|
2587
2688
|
const lines = [
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2689
|
+
`\u250C${sep.map((s) => `\u2500${s}\u2500`).join("\u252C")}\u2510`,
|
|
2690
|
+
`\u2502${header.map((h) => ` ${h} `).join("\u2502")}\u2502`,
|
|
2691
|
+
`\u251C${sep.map((s) => `\u2500${s}\u2500`).join("\u253C")}\u2524`
|
|
2591
2692
|
];
|
|
2592
2693
|
for (const row of rows) {
|
|
2593
2694
|
const cells = [
|
|
2594
2695
|
row.label.padEnd(14),
|
|
2595
2696
|
...row.values.map((v) => v.padEnd(colW))
|
|
2596
2697
|
];
|
|
2597
|
-
lines.push(
|
|
2698
|
+
lines.push(`\u2502${cells.map((c) => ` ${c} `).join("\u2502")}\u2502`);
|
|
2598
2699
|
}
|
|
2599
|
-
lines.push(
|
|
2700
|
+
lines.push(`\u2514${sep.map((s) => `\u2500${s}\u2500`).join("\u2534")}\u2518`);
|
|
2600
2701
|
return lines.join("\n");
|
|
2601
2702
|
}
|
|
2602
2703
|
function comparisonRepo(r) {
|
|
@@ -2852,8 +2953,8 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
2852
2953
|
// src/tui.ts
|
|
2853
2954
|
import {
|
|
2854
2955
|
createCliRenderer as createCliRenderer3,
|
|
2855
|
-
BoxRenderable as
|
|
2856
|
-
TextRenderable as
|
|
2956
|
+
BoxRenderable as BoxRenderable5,
|
|
2957
|
+
TextRenderable as TextRenderable5,
|
|
2857
2958
|
InputRenderable,
|
|
2858
2959
|
SelectRenderable,
|
|
2859
2960
|
ScrollBoxRenderable as ScrollBoxRenderable3
|
|
@@ -2958,7 +3059,7 @@ function buildHelpSections() {
|
|
|
2958
3059
|
{ keys: "Limit", action: "Cycle limit: 10 \u2192 25 \u2192 50 \u2192 100" },
|
|
2959
3060
|
{ keys: "Refresh", action: "Re-run current search" },
|
|
2960
3061
|
{ keys: "Graph", action: "Toggle commit activity chart" },
|
|
2961
|
-
{ keys: "Readme", action: "Full README viewer" },
|
|
3062
|
+
{ keys: "Readme", action: "Full markdown README viewer" },
|
|
2962
3063
|
{ keys: "Deep-dive", action: "Languages + contributors + excerpt" },
|
|
2963
3064
|
{ keys: "Bookmark", action: "Save / unsave selected repo" },
|
|
2964
3065
|
{ keys: "Compare", action: "Add to / remove from comparison" },
|
|
@@ -2998,6 +3099,7 @@ function buildHelpSections() {
|
|
|
2998
3099
|
{ keys: "d", action: "Delete entry (history, saved)" },
|
|
2999
3100
|
{ keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" },
|
|
3000
3101
|
{ keys: "\u2190 \u2192 / h l", action: "Navigate tabs / panels" },
|
|
3102
|
+
{ keys: "i", action: "Toggle inline images (README viewer)" },
|
|
3001
3103
|
{
|
|
3002
3104
|
keys: "PgUp/PgDn",
|
|
3003
3105
|
action: "Scroll update notes; Home/End jump to ends"
|
|
@@ -3074,7 +3176,8 @@ function deleteHistoryEntry(index) {
|
|
|
3074
3176
|
);
|
|
3075
3177
|
writeRaw(
|
|
3076
3178
|
HISTORY_FILE,
|
|
3077
|
-
remaining.reverse().map((e) => JSON.stringify(e)).join("\n")
|
|
3179
|
+
`${remaining.reverse().map((e) => JSON.stringify(e)).join("\n")}
|
|
3180
|
+
`
|
|
3078
3181
|
);
|
|
3079
3182
|
}
|
|
3080
3183
|
function clearHistory() {
|
|
@@ -3086,7 +3189,8 @@ function rotateHistory() {
|
|
|
3086
3189
|
const trimmed = entries.slice(0, MAX_ENTRIES);
|
|
3087
3190
|
writeRaw(
|
|
3088
3191
|
HISTORY_FILE,
|
|
3089
|
-
trimmed.reverse().map((e) => JSON.stringify(e)).join("\n")
|
|
3192
|
+
`${trimmed.reverse().map((e) => JSON.stringify(e)).join("\n")}
|
|
3193
|
+
`
|
|
3090
3194
|
);
|
|
3091
3195
|
}
|
|
3092
3196
|
|
|
@@ -3157,170 +3261,731 @@ function restoreSession() {
|
|
|
3157
3261
|
return readJSON(SESSION_FILE, null);
|
|
3158
3262
|
}
|
|
3159
3263
|
|
|
3160
|
-
// src/markdown-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3264
|
+
// src/markdown-view.ts
|
|
3265
|
+
import {
|
|
3266
|
+
BoxRenderable as BoxRenderable3,
|
|
3267
|
+
MarkdownRenderable,
|
|
3268
|
+
RGBA,
|
|
3269
|
+
StyledText as StyledText3,
|
|
3270
|
+
SyntaxStyle,
|
|
3271
|
+
TextRenderable as TextRenderable3,
|
|
3272
|
+
createTextAttributes
|
|
3273
|
+
} from "@opentui/core";
|
|
3274
|
+
|
|
3275
|
+
// src/png.ts
|
|
3276
|
+
import { inflateSync } from "zlib";
|
|
3277
|
+
var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
3278
|
+
function channelsForColorType(colorType) {
|
|
3279
|
+
switch (colorType) {
|
|
3280
|
+
case 0:
|
|
3281
|
+
return 1;
|
|
3282
|
+
// greyscale
|
|
3283
|
+
case 2:
|
|
3284
|
+
return 3;
|
|
3285
|
+
// truecolour
|
|
3286
|
+
case 3:
|
|
3287
|
+
return 1;
|
|
3288
|
+
// palette index
|
|
3289
|
+
case 4:
|
|
3290
|
+
return 2;
|
|
3291
|
+
// greyscale + alpha
|
|
3292
|
+
case 6:
|
|
3293
|
+
return 4;
|
|
3294
|
+
// truecolour + alpha
|
|
3295
|
+
default:
|
|
3296
|
+
throw new Error(`Unsupported PNG colour type ${colorType}`);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
function paeth(a, b, c) {
|
|
3300
|
+
const p = a + b - c;
|
|
3301
|
+
const pa = Math.abs(p - a);
|
|
3302
|
+
const pb = Math.abs(p - b);
|
|
3303
|
+
const pc = Math.abs(p - c);
|
|
3304
|
+
if (pa <= pb && pa <= pc) return a;
|
|
3305
|
+
if (pb <= pc) return b;
|
|
3306
|
+
return c;
|
|
3307
|
+
}
|
|
3308
|
+
function unfilter(raw, height, bytesPerRow, bytesPerPixel) {
|
|
3309
|
+
const out = new Uint8Array(height * bytesPerRow);
|
|
3310
|
+
let src = 0;
|
|
3311
|
+
for (let y = 0; y < height; y++) {
|
|
3312
|
+
const filter = raw[src++];
|
|
3313
|
+
const rowStart = y * bytesPerRow;
|
|
3314
|
+
const prevStart = rowStart - bytesPerRow;
|
|
3315
|
+
for (let x = 0; x < bytesPerRow; x++) {
|
|
3316
|
+
const value = raw[src + x];
|
|
3317
|
+
const left = x >= bytesPerPixel ? out[rowStart + x - bytesPerPixel] : 0;
|
|
3318
|
+
const up = y > 0 ? out[prevStart + x] : 0;
|
|
3319
|
+
const upLeft = y > 0 && x >= bytesPerPixel ? out[prevStart + x - bytesPerPixel] : 0;
|
|
3320
|
+
let result;
|
|
3321
|
+
switch (filter) {
|
|
3322
|
+
case 0:
|
|
3323
|
+
result = value;
|
|
3324
|
+
break;
|
|
3325
|
+
case 1:
|
|
3326
|
+
result = value + left;
|
|
3327
|
+
break;
|
|
3328
|
+
case 2:
|
|
3329
|
+
result = value + up;
|
|
3330
|
+
break;
|
|
3331
|
+
case 3:
|
|
3332
|
+
result = value + (left + up >> 1);
|
|
3333
|
+
break;
|
|
3334
|
+
case 4:
|
|
3335
|
+
result = value + paeth(left, up, upLeft);
|
|
3336
|
+
break;
|
|
3337
|
+
default:
|
|
3338
|
+
throw new Error(`Unsupported PNG filter type ${filter}`);
|
|
3339
|
+
}
|
|
3340
|
+
out[rowStart + x] = result & 255;
|
|
3341
|
+
}
|
|
3342
|
+
src += bytesPerRow;
|
|
3343
|
+
}
|
|
3344
|
+
return out;
|
|
3345
|
+
}
|
|
3346
|
+
function sampleAt(row, offsetInRow, index, bitDepth) {
|
|
3347
|
+
if (bitDepth === 8) return row[offsetInRow + index];
|
|
3348
|
+
if (bitDepth === 16) return row[offsetInRow + index * 2];
|
|
3349
|
+
const bitOffset = index * bitDepth;
|
|
3350
|
+
const byte = row[offsetInRow + (bitOffset >> 3)];
|
|
3351
|
+
const shift = 8 - bitDepth - (bitOffset & 7);
|
|
3352
|
+
return byte >> shift & (1 << bitDepth) - 1;
|
|
3353
|
+
}
|
|
3354
|
+
function decodePng(bytes) {
|
|
3355
|
+
if (bytes.length < 8) throw new Error("PNG too short");
|
|
3356
|
+
for (let i = 0; i < PNG_SIGNATURE.length; i++) {
|
|
3357
|
+
if (bytes[i] !== PNG_SIGNATURE[i]) throw new Error("Not a PNG file");
|
|
3358
|
+
}
|
|
3359
|
+
let width = 0;
|
|
3360
|
+
let height = 0;
|
|
3361
|
+
let bitDepth = 8;
|
|
3362
|
+
let colorType = 6;
|
|
3363
|
+
let interlace = 0;
|
|
3364
|
+
let palette = null;
|
|
3365
|
+
let transparency = null;
|
|
3366
|
+
const idat = [];
|
|
3367
|
+
let offset = 8;
|
|
3368
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3369
|
+
while (offset + 8 <= bytes.length) {
|
|
3370
|
+
const length = view.getUint32(offset);
|
|
3371
|
+
const type = String.fromCharCode(
|
|
3372
|
+
bytes[offset + 4],
|
|
3373
|
+
bytes[offset + 5],
|
|
3374
|
+
bytes[offset + 6],
|
|
3375
|
+
bytes[offset + 7]
|
|
3188
3376
|
);
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3377
|
+
const dataStart = offset + 8;
|
|
3378
|
+
const dataEnd = dataStart + length;
|
|
3379
|
+
if (dataEnd > bytes.length) throw new Error("Truncated PNG chunk");
|
|
3380
|
+
switch (type) {
|
|
3381
|
+
case "IHDR": {
|
|
3382
|
+
width = view.getUint32(dataStart);
|
|
3383
|
+
height = view.getUint32(dataStart + 4);
|
|
3384
|
+
bitDepth = bytes[dataStart + 8];
|
|
3385
|
+
colorType = bytes[dataStart + 9];
|
|
3386
|
+
interlace = bytes[dataStart + 12];
|
|
3387
|
+
break;
|
|
3388
|
+
}
|
|
3389
|
+
case "PLTE":
|
|
3390
|
+
palette = bytes.subarray(dataStart, dataEnd);
|
|
3391
|
+
break;
|
|
3392
|
+
case "tRNS":
|
|
3393
|
+
transparency = bytes.subarray(dataStart, dataEnd);
|
|
3394
|
+
break;
|
|
3395
|
+
case "IDAT":
|
|
3396
|
+
idat.push(bytes.subarray(dataStart, dataEnd));
|
|
3397
|
+
break;
|
|
3398
|
+
default:
|
|
3399
|
+
break;
|
|
3197
3400
|
}
|
|
3198
|
-
|
|
3199
|
-
if (
|
|
3200
|
-
|
|
3201
|
-
|
|
3401
|
+
offset = dataEnd + 4;
|
|
3402
|
+
if (type === "IEND") break;
|
|
3403
|
+
}
|
|
3404
|
+
if (width === 0 || height === 0) throw new Error("PNG has no IHDR");
|
|
3405
|
+
if (interlace !== 0) throw new Error("Interlaced PNG is not supported");
|
|
3406
|
+
const channels = channelsForColorType(colorType);
|
|
3407
|
+
const bitsPerPixel = channels * bitDepth;
|
|
3408
|
+
const bytesPerRow = Math.ceil(width * bitsPerPixel / 8);
|
|
3409
|
+
const bytesPerPixel = Math.max(1, Math.ceil(bitsPerPixel / 8));
|
|
3410
|
+
const compressed = idat.length === 1 ? idat[0] : concat(idat);
|
|
3411
|
+
const raw = new Uint8Array(inflateSync(compressed));
|
|
3412
|
+
if (raw.length < height * (bytesPerRow + 1)) {
|
|
3413
|
+
throw new Error("Truncated PNG pixel data");
|
|
3414
|
+
}
|
|
3415
|
+
const rows = unfilter(raw, height, bytesPerRow, bytesPerPixel);
|
|
3416
|
+
const out = new Uint8Array(width * height * 4);
|
|
3417
|
+
const maxSample = (1 << Math.min(bitDepth, 8)) - 1;
|
|
3418
|
+
const scale = bitDepth === 16 ? 1 / 257 : 255 / maxSample;
|
|
3419
|
+
for (let y = 0; y < height; y++) {
|
|
3420
|
+
const row = rows.subarray(y * bytesPerRow, (y + 1) * bytesPerRow);
|
|
3421
|
+
for (let x = 0; x < width; x++) {
|
|
3422
|
+
const to8 = (sample) => bitDepth === 8 || bitDepth === 16 ? sample : Math.round(sample * scale);
|
|
3423
|
+
let r;
|
|
3424
|
+
let g;
|
|
3425
|
+
let b;
|
|
3426
|
+
let a = 255;
|
|
3427
|
+
switch (colorType) {
|
|
3428
|
+
case 0: {
|
|
3429
|
+
const grey = to8(sampleAt(row, 0, x, bitDepth));
|
|
3430
|
+
r = g = b = grey;
|
|
3431
|
+
break;
|
|
3432
|
+
}
|
|
3433
|
+
case 2:
|
|
3434
|
+
r = to8(sampleAt(row, 0, x * 3, bitDepth));
|
|
3435
|
+
g = to8(sampleAt(row, 0, x * 3 + 1, bitDepth));
|
|
3436
|
+
b = to8(sampleAt(row, 0, x * 3 + 2, bitDepth));
|
|
3437
|
+
break;
|
|
3438
|
+
case 3: {
|
|
3439
|
+
const index = sampleAt(row, 0, x, bitDepth);
|
|
3440
|
+
const p = index * 3;
|
|
3441
|
+
if (!palette || p + 2 >= palette.length)
|
|
3442
|
+
throw new Error("PNG palette entry missing");
|
|
3443
|
+
r = palette[p];
|
|
3444
|
+
g = palette[p + 1];
|
|
3445
|
+
b = palette[p + 2];
|
|
3446
|
+
if (transparency && index < transparency.length)
|
|
3447
|
+
a = transparency[index];
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
case 4: {
|
|
3451
|
+
const grey = to8(sampleAt(row, 0, x * 2, bitDepth));
|
|
3452
|
+
r = g = b = grey;
|
|
3453
|
+
a = to8(sampleAt(row, 0, x * 2 + 1, bitDepth));
|
|
3454
|
+
break;
|
|
3455
|
+
}
|
|
3456
|
+
default: {
|
|
3457
|
+
r = to8(sampleAt(row, 0, x * 4, bitDepth));
|
|
3458
|
+
g = to8(sampleAt(row, 0, x * 4 + 1, bitDepth));
|
|
3459
|
+
b = to8(sampleAt(row, 0, x * 4 + 2, bitDepth));
|
|
3460
|
+
a = to8(sampleAt(row, 0, x * 4 + 3, bitDepth));
|
|
3461
|
+
break;
|
|
3462
|
+
}
|
|
3202
3463
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
out.push(sep);
|
|
3208
|
-
dataRows.forEach((row, i) => {
|
|
3209
|
-
const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
|
|
3210
|
-
out.push("\u2502 " + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
|
|
3211
|
-
if (i === 0) out.push(mid);
|
|
3212
|
-
});
|
|
3213
|
-
out.push(end);
|
|
3214
|
-
}
|
|
3215
|
-
out.push("");
|
|
3216
|
-
tableRows = [];
|
|
3217
|
-
}
|
|
3218
|
-
function renderInline(line) {
|
|
3219
|
-
line = line.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
3220
|
-
line = line.replace(/\*([^*]+?)\*/g, "$1");
|
|
3221
|
-
line = line.replace(/`([^`]+)`/g, "\u27E8$1\u27E9");
|
|
3222
|
-
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
3223
|
-
line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "\u{1F5BC} $1");
|
|
3224
|
-
return line;
|
|
3225
|
-
}
|
|
3226
|
-
function wrapLine(line, maxW) {
|
|
3227
|
-
if (line.length <= maxW) return [line];
|
|
3228
|
-
const parts = [];
|
|
3229
|
-
let remaining = line;
|
|
3230
|
-
while (remaining.length > maxW) {
|
|
3231
|
-
let breakAt = maxW;
|
|
3232
|
-
const spaceIdx = remaining.lastIndexOf(" ", maxW);
|
|
3233
|
-
if (spaceIdx > maxW / 2) breakAt = spaceIdx;
|
|
3234
|
-
parts.push(remaining.slice(0, breakAt));
|
|
3235
|
-
remaining = remaining.slice(breakAt).replace(/^ /, "");
|
|
3236
|
-
}
|
|
3237
|
-
if (remaining) parts.push(remaining);
|
|
3238
|
-
return parts;
|
|
3239
|
-
}
|
|
3240
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3241
|
-
let line = lines[i];
|
|
3242
|
-
if (/^```/.test(line)) {
|
|
3243
|
-
if (inCodeBlock) {
|
|
3244
|
-
flushCode();
|
|
3245
|
-
inCodeBlock = false;
|
|
3246
|
-
} else {
|
|
3247
|
-
inCodeBlock = true;
|
|
3248
|
-
codeLang = line.slice(3).trim();
|
|
3464
|
+
if (colorType === 0 && transparency && transparency.length >= 2) {
|
|
3465
|
+
const key = transparency[0] << 8 | transparency[1];
|
|
3466
|
+
const sample = sampleAt(row, 0, x, bitDepth);
|
|
3467
|
+
if (sample === key) a = 0;
|
|
3249
3468
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3469
|
+
const o = (y * width + x) * 4;
|
|
3470
|
+
out[o] = r;
|
|
3471
|
+
out[o + 1] = g;
|
|
3472
|
+
out[o + 2] = b;
|
|
3473
|
+
out[o + 3] = a;
|
|
3255
3474
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3475
|
+
}
|
|
3476
|
+
return { width, height, data: out };
|
|
3477
|
+
}
|
|
3478
|
+
function concat(chunks) {
|
|
3479
|
+
let total = 0;
|
|
3480
|
+
for (const c of chunks) total += c.length;
|
|
3481
|
+
const merged = new Uint8Array(total);
|
|
3482
|
+
let offset = 0;
|
|
3483
|
+
for (const c of chunks) {
|
|
3484
|
+
merged.set(c, offset);
|
|
3485
|
+
offset += c.length;
|
|
3486
|
+
}
|
|
3487
|
+
return merged;
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// src/image-render.ts
|
|
3491
|
+
var DEFAULT_CELL_ASPECT = 0.5;
|
|
3492
|
+
var HALF_BLOCK = "\u2580";
|
|
3493
|
+
function fitImageToCells(imgWidth, imgHeight, opts) {
|
|
3494
|
+
const cellAspect = opts.cellAspect ?? DEFAULT_CELL_ASPECT;
|
|
3495
|
+
const minCols = Math.max(1, Math.min(opts.maxCols, 8));
|
|
3496
|
+
if (imgWidth <= 0 || imgHeight <= 0) {
|
|
3497
|
+
return { cols: minCols, rows: 1 };
|
|
3498
|
+
}
|
|
3499
|
+
const rowsPerCol = cellAspect * (imgHeight / imgWidth);
|
|
3500
|
+
let cols = Math.max(minCols, Math.floor(opts.maxCols));
|
|
3501
|
+
let rows = Math.max(1, Math.round(cols * rowsPerCol));
|
|
3502
|
+
if (rows > opts.maxRows) {
|
|
3503
|
+
rows = Math.max(1, opts.maxRows);
|
|
3504
|
+
cols = Math.max(
|
|
3505
|
+
minCols,
|
|
3506
|
+
Math.min(cols, Math.floor(rows / Math.max(rowsPerCol, 1e-6)))
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
return { cols, rows };
|
|
3510
|
+
}
|
|
3511
|
+
function pixelAt(img, x, y, background) {
|
|
3512
|
+
const o = (y * img.width + x) * 4;
|
|
3513
|
+
const a = img.data[o + 3] / 255;
|
|
3514
|
+
if (a >= 1) return [img.data[o], img.data[o + 1], img.data[o + 2]];
|
|
3515
|
+
const mix = (channel, bg) => Math.round(channel * a + bg * (1 - a));
|
|
3516
|
+
return [
|
|
3517
|
+
mix(img.data[o], background[0]),
|
|
3518
|
+
mix(img.data[o + 1], background[1]),
|
|
3519
|
+
mix(img.data[o + 2], background[2])
|
|
3520
|
+
];
|
|
3521
|
+
}
|
|
3522
|
+
function toHalfBlocks(img, opts = {}) {
|
|
3523
|
+
const background = opts.background ?? [0, 0, 0];
|
|
3524
|
+
const cells = [];
|
|
3525
|
+
for (let row = 0; row * 2 < img.height; row++) {
|
|
3526
|
+
const cellsRow = [];
|
|
3527
|
+
const topY = row * 2;
|
|
3528
|
+
const bottomY = topY + 1;
|
|
3529
|
+
for (let x = 0; x < img.width; x++) {
|
|
3530
|
+
const top = pixelAt(img, x, topY, background);
|
|
3531
|
+
const bottom = bottomY < img.height ? pixelAt(img, x, bottomY, background) : top;
|
|
3532
|
+
cellsRow.push({ top, bottom });
|
|
3533
|
+
}
|
|
3534
|
+
cells.push(cellsRow);
|
|
3535
|
+
}
|
|
3536
|
+
return { cols: img.width, rows: cells.length, cells };
|
|
3537
|
+
}
|
|
3538
|
+
function toImageRuns(image) {
|
|
3539
|
+
return image.cells.map((cells) => {
|
|
3540
|
+
const runs = [];
|
|
3541
|
+
for (const cell of cells) {
|
|
3542
|
+
const last = runs[runs.length - 1];
|
|
3543
|
+
if (last && last.top[0] === cell.top[0] && last.top[1] === cell.top[1] && last.top[2] === cell.top[2] && last.bottom[0] === cell.bottom[0] && last.bottom[1] === cell.bottom[1] && last.bottom[2] === cell.bottom[2]) {
|
|
3544
|
+
last.text += HALF_BLOCK;
|
|
3261
3545
|
continue;
|
|
3262
3546
|
}
|
|
3263
|
-
|
|
3264
|
-
flushTable();
|
|
3265
|
-
inTable = false;
|
|
3547
|
+
runs.push({ text: HALF_BLOCK, top: cell.top, bottom: cell.bottom });
|
|
3266
3548
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3549
|
+
return runs;
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
var MAX_SOURCE_PIXELS = 2e7;
|
|
3553
|
+
async function rasterizeImage(bytes, opts) {
|
|
3554
|
+
const ImageCtor = globalThis.Bun?.Image;
|
|
3555
|
+
if (!ImageCtor) return null;
|
|
3556
|
+
const source = new ImageCtor(bytes, { maxPixels: MAX_SOURCE_PIXELS });
|
|
3557
|
+
try {
|
|
3558
|
+
const meta = await source.metadata();
|
|
3559
|
+
const { cols, rows } = fitImageToCells(meta.width, meta.height, opts);
|
|
3560
|
+
const png = await source.resize(cols, rows * 2, { fit: "fill" }).png({ compressionLevel: 1 }).bytes();
|
|
3561
|
+
return toHalfBlocks(decodePng(png), { background: opts.background });
|
|
3562
|
+
} catch {
|
|
3563
|
+
return null;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
// src/image-loader.ts
|
|
3568
|
+
var MAX_IMAGE_BYTES = 6 * 1024 * 1024;
|
|
3569
|
+
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;
|
|
3570
|
+
var RAW_HOSTS = /* @__PURE__ */ new Set([
|
|
3571
|
+
"raw.githubusercontent.com",
|
|
3572
|
+
"camo.githubusercontent.com",
|
|
3573
|
+
"user-images.githubusercontent.com",
|
|
3574
|
+
"private-user-images.githubusercontent.com",
|
|
3575
|
+
"avatars.githubusercontent.com",
|
|
3576
|
+
"objects.githubusercontent.com"
|
|
3577
|
+
]);
|
|
3578
|
+
var RASTER_EXT = /\.(png|jpe?g|webp|gif|bmp|avif|heic|tiff?)(?:[?#]|$)/i;
|
|
3579
|
+
var CACHE_LIMIT = 24;
|
|
3580
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3581
|
+
function decodeDataUri(uri) {
|
|
3582
|
+
const match = /^data:([^;,]+)?(;base64)?,(.*)$/is.exec(uri);
|
|
3583
|
+
if (!match) return null;
|
|
3584
|
+
const mime = (match[1] ?? "").toLowerCase();
|
|
3585
|
+
if (mime.includes("svg")) return null;
|
|
3586
|
+
const payload = match[3];
|
|
3587
|
+
try {
|
|
3588
|
+
if (match[2]) return new Uint8Array(Buffer.from(payload, "base64"));
|
|
3589
|
+
return new Uint8Array(Buffer.from(decodeURIComponent(payload), "binary"));
|
|
3590
|
+
} catch {
|
|
3591
|
+
return null;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
function toRawGitHubUrl(url) {
|
|
3595
|
+
if (url.hostname.toLowerCase() !== "github.com") return url;
|
|
3596
|
+
const match = /^\/([^/]+)\/([^/]+)\/(?:blob|raw)\/(.+)$/.exec(url.pathname);
|
|
3597
|
+
if (!match) return url;
|
|
3598
|
+
const [, owner, repo, rest] = match;
|
|
3599
|
+
return new URL(
|
|
3600
|
+
`https://raw.githubusercontent.com/${owner}/${repo}/${rest}${url.search}`
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3603
|
+
function resolveRootRelative(path, base) {
|
|
3604
|
+
let parsed;
|
|
3605
|
+
try {
|
|
3606
|
+
parsed = new URL(base);
|
|
3607
|
+
} catch {
|
|
3608
|
+
return null;
|
|
3609
|
+
}
|
|
3610
|
+
if (parsed.hostname.toLowerCase() !== "raw.githubusercontent.com") {
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
3614
|
+
if (segments.length < 3) return null;
|
|
3615
|
+
return new URL(`${parsed.origin}/${segments.slice(0, 3).join("/")}${path}`);
|
|
3616
|
+
}
|
|
3617
|
+
function resolveReadmeImageUrl(raw, base) {
|
|
3618
|
+
const cleaned = raw.trim();
|
|
3619
|
+
if (!cleaned) return null;
|
|
3620
|
+
if (/^data:/i.test(cleaned)) {
|
|
3621
|
+
return isRenderableImageUrl(cleaned) ? cleaned : null;
|
|
3622
|
+
}
|
|
3623
|
+
let url = null;
|
|
3624
|
+
try {
|
|
3625
|
+
url = cleaned.startsWith("/") ? resolveRootRelative(cleaned, base) : new URL(cleaned, base);
|
|
3626
|
+
} catch {
|
|
3627
|
+
return null;
|
|
3628
|
+
}
|
|
3629
|
+
if (!url) return null;
|
|
3630
|
+
url = toRawGitHubUrl(url);
|
|
3631
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
3632
|
+
url.hash = "";
|
|
3633
|
+
const resolved = url.toString();
|
|
3634
|
+
return isRenderableImageUrl(resolved) ? resolved : null;
|
|
3635
|
+
}
|
|
3636
|
+
function isRenderableImageUrl(url) {
|
|
3637
|
+
if (/^data:/i.test(url)) return !/svg/i.test(url.slice(0, 64));
|
|
3638
|
+
let parsed;
|
|
3639
|
+
try {
|
|
3640
|
+
parsed = new URL(url);
|
|
3641
|
+
} catch {
|
|
3642
|
+
return false;
|
|
3643
|
+
}
|
|
3644
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
3645
|
+
if (BADGE_HOST.test(parsed.hostname)) return false;
|
|
3646
|
+
const path = parsed.pathname.toLowerCase();
|
|
3647
|
+
if (path.endsWith(".svg") || path.endsWith(".svgz")) return false;
|
|
3648
|
+
if (parsed.hostname.toLowerCase().endsWith("github.com")) {
|
|
3649
|
+
if (/^\/user-attachments\//.test(path)) return true;
|
|
3650
|
+
if (!RAW_HOSTS.has(parsed.hostname)) {
|
|
3651
|
+
return /\.(png|jpe?g|webp|gif|bmp|avif|heic|tiff?)$/i.test(path);
|
|
3271
3652
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3653
|
+
}
|
|
3654
|
+
if (/\.[a-z0-9]{1,5}$/.test(path) && !RASTER_EXT.test(path)) return false;
|
|
3655
|
+
return true;
|
|
3656
|
+
}
|
|
3657
|
+
function isGitHubHost(hostname) {
|
|
3658
|
+
return /(^|\.)githubusercontent\.com$|(^|\.)github\.com$/i.test(hostname);
|
|
3659
|
+
}
|
|
3660
|
+
async function fetchImageBytes(url, opts = {}) {
|
|
3661
|
+
const cached2 = cache.get(url);
|
|
3662
|
+
if (cached2) return cached2;
|
|
3663
|
+
const pending = loadImageBytes(url, opts);
|
|
3664
|
+
cache.set(url, pending);
|
|
3665
|
+
while (cache.size > CACHE_LIMIT) {
|
|
3666
|
+
const oldest = cache.keys().next().value;
|
|
3667
|
+
if (oldest === void 0) break;
|
|
3668
|
+
cache.delete(oldest);
|
|
3669
|
+
}
|
|
3670
|
+
return pending;
|
|
3671
|
+
}
|
|
3672
|
+
async function loadImageBytes(url, opts) {
|
|
3673
|
+
if (/^data:/i.test(url)) return decodeDataUri(url);
|
|
3674
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
3675
|
+
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
3676
|
+
const controller = new AbortController();
|
|
3677
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3678
|
+
try {
|
|
3679
|
+
const headers = {
|
|
3680
|
+
"User-Agent": "ghfind/1.0",
|
|
3681
|
+
Accept: "image/*,*/*;q=0.5"
|
|
3682
|
+
};
|
|
3683
|
+
let host = "";
|
|
3684
|
+
try {
|
|
3685
|
+
host = new URL(url).hostname;
|
|
3686
|
+
} catch {
|
|
3687
|
+
return null;
|
|
3281
3688
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3689
|
+
if (opts.token && isGitHubHost(host)) {
|
|
3690
|
+
headers.Authorization = `Bearer ${opts.token}`;
|
|
3691
|
+
}
|
|
3692
|
+
const res = await fetchImpl(url, { headers, signal: controller.signal });
|
|
3693
|
+
if (!res.ok) return null;
|
|
3694
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
3695
|
+
if (contentType && !/^image\//i.test(contentType)) {
|
|
3696
|
+
const githubHost = isGitHubHost(host);
|
|
3697
|
+
if (!githubHost || !/octet-stream/i.test(contentType)) return null;
|
|
3698
|
+
}
|
|
3699
|
+
if (/svg/i.test(contentType)) return null;
|
|
3700
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
3701
|
+
if (declared > MAX_IMAGE_BYTES) return null;
|
|
3702
|
+
const buffer = new Uint8Array(await res.arrayBuffer());
|
|
3703
|
+
if (buffer.byteLength === 0 || buffer.byteLength > MAX_IMAGE_BYTES) {
|
|
3704
|
+
return null;
|
|
3705
|
+
}
|
|
3706
|
+
return buffer;
|
|
3707
|
+
} catch {
|
|
3708
|
+
return null;
|
|
3709
|
+
} finally {
|
|
3710
|
+
clearTimeout(timer);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
function readmeBaseUrl(owner, repo, branch, path = "README.md") {
|
|
3714
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
// src/markdown-images.ts
|
|
3718
|
+
var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
3719
|
+
var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
|
|
3720
|
+
var HTML_TAG = /<\/?[a-z][^>]*>/gi;
|
|
3721
|
+
function imagesFromHtml(raw) {
|
|
3722
|
+
if (!raw || !/<img\b/i.test(raw)) return [];
|
|
3723
|
+
const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
|
|
3724
|
+
if (withoutTags) return [];
|
|
3725
|
+
const refs = [];
|
|
3726
|
+
for (const match of raw.matchAll(IMG_TAG)) {
|
|
3727
|
+
const tag = match[0];
|
|
3728
|
+
const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
|
|
3729
|
+
refs.push({ href: match[1], alt });
|
|
3730
|
+
}
|
|
3731
|
+
return refs;
|
|
3732
|
+
}
|
|
3733
|
+
function inlinePlainText(tokens) {
|
|
3734
|
+
if (!tokens) return "";
|
|
3735
|
+
let out = "";
|
|
3736
|
+
for (const token of tokens) {
|
|
3737
|
+
if (token.type === "image") continue;
|
|
3738
|
+
if (token.type === "html") {
|
|
3739
|
+
out += (token.raw ?? "").replace(HTML_TAG, "");
|
|
3288
3740
|
continue;
|
|
3289
3741
|
}
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
const indent = ulMatch[1].length;
|
|
3293
|
-
const depth = Math.floor(indent / 2);
|
|
3294
|
-
const prefix = " ".repeat(depth) + "\u2022 ";
|
|
3295
|
-
const inner = renderInline(ulMatch[2]);
|
|
3296
|
-
for (const wl of wrapLine(inner, width - prefix.length)) {
|
|
3297
|
-
out.push(prefix + wl);
|
|
3298
|
-
}
|
|
3742
|
+
if (token.type === "br") {
|
|
3743
|
+
out += "\n";
|
|
3299
3744
|
continue;
|
|
3300
3745
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
const indent = olMatch[1].length;
|
|
3304
|
-
const depth = Math.floor(indent / 2);
|
|
3305
|
-
const prefix = " ".repeat(depth) + "\u25E6 ";
|
|
3306
|
-
const inner = renderInline(olMatch[2]);
|
|
3307
|
-
for (const wl of wrapLine(inner, width - prefix.length)) {
|
|
3308
|
-
out.push(prefix + wl);
|
|
3309
|
-
}
|
|
3746
|
+
if (token.text && (!token.tokens || token.tokens.length === 0)) {
|
|
3747
|
+
out += token.text;
|
|
3310
3748
|
continue;
|
|
3311
3749
|
}
|
|
3312
|
-
if (
|
|
3313
|
-
out.
|
|
3750
|
+
if (token.tokens?.length) {
|
|
3751
|
+
out += inlinePlainText(token.tokens);
|
|
3314
3752
|
continue;
|
|
3315
3753
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3754
|
+
if (token.raw) out += token.raw.replace(HTML_TAG, "");
|
|
3755
|
+
}
|
|
3756
|
+
return out;
|
|
3757
|
+
}
|
|
3758
|
+
function extractImageRefs(token) {
|
|
3759
|
+
const images = [];
|
|
3760
|
+
const collect = (inline2) => {
|
|
3761
|
+
if (inline2.type === "image") {
|
|
3762
|
+
if (inline2.href)
|
|
3763
|
+
images.push({ href: inline2.href, alt: inline2.text ?? "" });
|
|
3764
|
+
return true;
|
|
3765
|
+
}
|
|
3766
|
+
if (inline2.type === "html") {
|
|
3767
|
+
const refs = imagesFromHtml(inline2.raw);
|
|
3768
|
+
if (refs.length > 0) {
|
|
3769
|
+
images.push(...refs);
|
|
3770
|
+
return true;
|
|
3771
|
+
}
|
|
3772
|
+
return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
|
|
3773
|
+
}
|
|
3774
|
+
if (inline2.type === "text" || inline2.type === "escape") {
|
|
3775
|
+
return !(inline2.text ?? "").trim();
|
|
3319
3776
|
}
|
|
3777
|
+
return false;
|
|
3778
|
+
};
|
|
3779
|
+
if (token.type === "image" && token.href) {
|
|
3780
|
+
return [{ href: token.href, alt: token.text ?? "" }];
|
|
3781
|
+
}
|
|
3782
|
+
if (token.type === "html") {
|
|
3783
|
+
const refs = imagesFromHtml(token.raw);
|
|
3784
|
+
return refs.length > 0 ? refs : null;
|
|
3320
3785
|
}
|
|
3321
|
-
|
|
3322
|
-
if (
|
|
3323
|
-
|
|
3786
|
+
const inline = token.tokens ?? [];
|
|
3787
|
+
if (inline.length === 0) return null;
|
|
3788
|
+
for (const child of inline) {
|
|
3789
|
+
if (!collect(child)) return null;
|
|
3790
|
+
}
|
|
3791
|
+
return images.length > 0 ? images : null;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
// src/markdown-view.ts
|
|
3795
|
+
var MAX_IMAGE_ROWS = 24;
|
|
3796
|
+
function createMarkdownStyle(colors2) {
|
|
3797
|
+
const surface = colors2.surface ?? colors2.bg;
|
|
3798
|
+
return SyntaxStyle.fromStyles({
|
|
3799
|
+
default: { fg: colors2.text ?? "#ffffff" },
|
|
3800
|
+
conceal: { fg: colors2.muted ?? "#888888" },
|
|
3801
|
+
"markup.heading": { fg: colors2.accent ?? colors2.blue, bold: true },
|
|
3802
|
+
"markup.strong": { fg: colors2.text ?? "#ffffff", bold: true },
|
|
3803
|
+
"markup.italic": { fg: colors2.text ?? "#ffffff", italic: true },
|
|
3804
|
+
"markup.strikethrough": { fg: colors2.muted ?? "#888888", dim: true },
|
|
3805
|
+
"markup.raw": { fg: colors2.green ?? "#a6e3a1", bg: surface },
|
|
3806
|
+
"markup.raw.block": { fg: colors2.green ?? "#a6e3a1" },
|
|
3807
|
+
"markup.link": { fg: colors2.accent ?? colors2.blue },
|
|
3808
|
+
"markup.link.label": {
|
|
3809
|
+
fg: colors2.accent ?? colors2.blue,
|
|
3810
|
+
underline: true
|
|
3811
|
+
},
|
|
3812
|
+
"markup.link.url": { fg: colors2.muted ?? "#888888", underline: true },
|
|
3813
|
+
"markup.quote": { fg: colors2.muted ?? "#888888", italic: true },
|
|
3814
|
+
"markup.list": { fg: colors2.blue ?? colors2.accent }
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
function hexToRgb(hex) {
|
|
3818
|
+
const fallback = [0, 0, 0];
|
|
3819
|
+
if (!hex) return fallback;
|
|
3820
|
+
const match = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
3821
|
+
if (!match) return fallback;
|
|
3822
|
+
const value = Number.parseInt(match[1], 16);
|
|
3823
|
+
return [value >> 16 & 255, value >> 8 & 255, value & 255];
|
|
3824
|
+
}
|
|
3825
|
+
function rgba(color) {
|
|
3826
|
+
return RGBA.fromValues(color[0], color[1], color[2], 255);
|
|
3827
|
+
}
|
|
3828
|
+
function imageStyledText(image) {
|
|
3829
|
+
const chunks = [];
|
|
3830
|
+
toImageRuns(image).forEach((runs, rowIndex) => {
|
|
3831
|
+
if (rowIndex > 0) chunks.push({ __isChunk: true, text: "\n" });
|
|
3832
|
+
for (const run2 of runs) {
|
|
3833
|
+
chunks.push({
|
|
3834
|
+
__isChunk: true,
|
|
3835
|
+
text: run2.text,
|
|
3836
|
+
fg: rgba(run2.top),
|
|
3837
|
+
bg: rgba(run2.bottom)
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
});
|
|
3841
|
+
return new StyledText3(chunks);
|
|
3842
|
+
}
|
|
3843
|
+
function createMarkdownView(opts) {
|
|
3844
|
+
const { renderer, colors: colors2 } = opts;
|
|
3845
|
+
const maxImageRows = opts.maxImageRows ?? MAX_IMAGE_ROWS;
|
|
3846
|
+
const rasterCache = /* @__PURE__ */ new Map();
|
|
3847
|
+
let imagesEnabled = opts.images ?? true;
|
|
3848
|
+
let imageBase = opts.imageBase;
|
|
3849
|
+
const text = () => colors2.text ?? "#ffffff";
|
|
3850
|
+
const bg = () => opts.background ?? colors2.bg ?? "#000000";
|
|
3851
|
+
const muted = () => colors2.muted ?? "#888888";
|
|
3852
|
+
const renderable = new MarkdownRenderable(renderer, {
|
|
3853
|
+
content: "",
|
|
3854
|
+
syntaxStyle: createMarkdownStyle(colors2),
|
|
3855
|
+
fg: text(),
|
|
3856
|
+
bg: bg(),
|
|
3857
|
+
conceal: true,
|
|
3858
|
+
streaming: true,
|
|
3859
|
+
internalBlockMode: "top-level",
|
|
3860
|
+
// Borderless columns are the top-level default; boxed tables read better
|
|
3861
|
+
// for the multi-column tables READMEs use.
|
|
3862
|
+
tableOptions: { style: "grid" },
|
|
3863
|
+
width: "100%"
|
|
3864
|
+
});
|
|
3865
|
+
const caption = (label, color = muted()) => new TextRenderable3(renderer, {
|
|
3866
|
+
content: ` ${label}`,
|
|
3867
|
+
fg: color,
|
|
3868
|
+
bg: bg(),
|
|
3869
|
+
wrapMode: "word",
|
|
3870
|
+
flexShrink: 0,
|
|
3871
|
+
width: "100%"
|
|
3872
|
+
});
|
|
3873
|
+
const rasterize = (url) => {
|
|
3874
|
+
const maxCols = Math.max(8, Math.floor(opts.getImageWidth?.() ?? 100));
|
|
3875
|
+
const key = `${maxCols}|${url}`;
|
|
3876
|
+
const cached2 = rasterCache.get(key);
|
|
3877
|
+
if (cached2) return cached2;
|
|
3878
|
+
const pending = (async () => {
|
|
3879
|
+
const loader = opts.loadImage;
|
|
3880
|
+
if (!loader) return null;
|
|
3881
|
+
const bytes = await loader(url);
|
|
3882
|
+
if (!bytes) return null;
|
|
3883
|
+
return rasterizeImage(bytes, {
|
|
3884
|
+
maxCols,
|
|
3885
|
+
maxRows: maxImageRows,
|
|
3886
|
+
background: hexToRgb(bg())
|
|
3887
|
+
});
|
|
3888
|
+
})();
|
|
3889
|
+
rasterCache.set(key, pending);
|
|
3890
|
+
return pending;
|
|
3891
|
+
};
|
|
3892
|
+
const imageNode = (image) => {
|
|
3893
|
+
const label = `\u{1F5BC} ${image.alt.trim() || "image"}`;
|
|
3894
|
+
const url = imageBase ? resolveReadmeImageUrl(image.href, imageBase) : null;
|
|
3895
|
+
if (!imagesEnabled || !url || !opts.loadImage) return caption(label);
|
|
3896
|
+
const container = new BoxRenderable3(renderer, {
|
|
3897
|
+
flexDirection: "column",
|
|
3898
|
+
flexShrink: 0,
|
|
3899
|
+
width: "100%"
|
|
3900
|
+
});
|
|
3901
|
+
const placeholder = caption(`${label} (loading\u2026)`);
|
|
3902
|
+
container.add(placeholder);
|
|
3903
|
+
void rasterize(url).then(
|
|
3904
|
+
(cells) => {
|
|
3905
|
+
console.error(
|
|
3906
|
+
`[dbg] image resolved: cells=${cells ? `${cells.cols}x${cells.rows}` : "null"} destroyed=${container.isDestroyed} parent=${!!container.parent} enabled=${imagesEnabled}`
|
|
3907
|
+
);
|
|
3908
|
+
if (container.isDestroyed || !container.parent) return;
|
|
3909
|
+
container.remove(placeholder);
|
|
3910
|
+
placeholder.destroy();
|
|
3911
|
+
container.add(caption(label));
|
|
3912
|
+
if (cells) {
|
|
3913
|
+
container.add(
|
|
3914
|
+
new TextRenderable3(renderer, {
|
|
3915
|
+
content: imageStyledText(cells),
|
|
3916
|
+
bg: bg(),
|
|
3917
|
+
wrapMode: "none",
|
|
3918
|
+
selectable: false,
|
|
3919
|
+
flexShrink: 0
|
|
3920
|
+
})
|
|
3921
|
+
);
|
|
3922
|
+
}
|
|
3923
|
+
renderable.requestRender();
|
|
3924
|
+
},
|
|
3925
|
+
(err) => {
|
|
3926
|
+
console.error("[dbg] image promise rejected:", err);
|
|
3927
|
+
}
|
|
3928
|
+
);
|
|
3929
|
+
return container;
|
|
3930
|
+
};
|
|
3931
|
+
const headingNode = (token) => {
|
|
3932
|
+
const heading = inlinePlainText(token.tokens).trim();
|
|
3933
|
+
if (!heading) return void 0;
|
|
3934
|
+
const depth = token.depth ?? 1;
|
|
3935
|
+
const color = depth <= 2 ? colors2.accent : colors2.teal ?? colors2.accent;
|
|
3936
|
+
return new TextRenderable3(renderer, {
|
|
3937
|
+
content: new StyledText3([
|
|
3938
|
+
{
|
|
3939
|
+
__isChunk: true,
|
|
3940
|
+
text: heading,
|
|
3941
|
+
fg: RGBA.fromHex(color ?? "#ffffff"),
|
|
3942
|
+
attributes: createTextAttributes({
|
|
3943
|
+
bold: true,
|
|
3944
|
+
underline: depth === 1
|
|
3945
|
+
})
|
|
3946
|
+
}
|
|
3947
|
+
]),
|
|
3948
|
+
bg: bg(),
|
|
3949
|
+
wrapMode: "word",
|
|
3950
|
+
flexShrink: 0,
|
|
3951
|
+
width: "100%"
|
|
3952
|
+
});
|
|
3953
|
+
};
|
|
3954
|
+
const buildRenderNode = () => (token, _context) => {
|
|
3955
|
+
if (token.type === "heading") return headingNode(token);
|
|
3956
|
+
const images = extractImageRefs(token);
|
|
3957
|
+
if (images && images.length > 0) {
|
|
3958
|
+
const box = new BoxRenderable3(renderer, {
|
|
3959
|
+
flexDirection: "column",
|
|
3960
|
+
flexShrink: 0,
|
|
3961
|
+
width: "100%",
|
|
3962
|
+
marginTop: 1
|
|
3963
|
+
});
|
|
3964
|
+
for (const image of images) box.add(imageNode(image));
|
|
3965
|
+
return box;
|
|
3966
|
+
}
|
|
3967
|
+
return void 0;
|
|
3968
|
+
};
|
|
3969
|
+
renderable.renderNode = buildRenderNode();
|
|
3970
|
+
return {
|
|
3971
|
+
renderable,
|
|
3972
|
+
get imagesEnabled() {
|
|
3973
|
+
return imagesEnabled;
|
|
3974
|
+
},
|
|
3975
|
+
setContent(markdown, base) {
|
|
3976
|
+
if (base !== void 0) imageBase = base;
|
|
3977
|
+
renderable.syntaxStyle = createMarkdownStyle(colors2);
|
|
3978
|
+
renderable.fg = text();
|
|
3979
|
+
renderable.bg = bg();
|
|
3980
|
+
renderable.content = markdown;
|
|
3981
|
+
},
|
|
3982
|
+
setImagesEnabled(enabled) {
|
|
3983
|
+
if (imagesEnabled === enabled) return;
|
|
3984
|
+
imagesEnabled = enabled;
|
|
3985
|
+
renderable.renderNode = buildRenderNode();
|
|
3986
|
+
renderable.requestRender();
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3324
3989
|
}
|
|
3325
3990
|
|
|
3326
3991
|
// src/explore.ts
|
|
@@ -3392,7 +4057,7 @@ var StatusManager = class {
|
|
|
3392
4057
|
};
|
|
3393
4058
|
|
|
3394
4059
|
// src/share.ts
|
|
3395
|
-
import { spawn as
|
|
4060
|
+
import { spawn as spawn3 } from "child_process";
|
|
3396
4061
|
function formatShare(repo, format2) {
|
|
3397
4062
|
switch (format2) {
|
|
3398
4063
|
case "markdown":
|
|
@@ -3410,7 +4075,7 @@ async function copyToClipboard(text) {
|
|
|
3410
4075
|
const p = process.platform;
|
|
3411
4076
|
const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await findLinuxClipboard();
|
|
3412
4077
|
if (!cmd) return false;
|
|
3413
|
-
const proc =
|
|
4078
|
+
const proc = spawn3(cmd[0], cmd.slice(1), {
|
|
3414
4079
|
stdio: ["pipe", "ignore", "ignore"]
|
|
3415
4080
|
});
|
|
3416
4081
|
proc.stdin.write(text);
|
|
@@ -3473,9 +4138,10 @@ function nextTip() {
|
|
|
3473
4138
|
|
|
3474
4139
|
// src/update-check.ts
|
|
3475
4140
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3476
|
-
import { join as join5, dirname as dirname3 } from "path";
|
|
4141
|
+
import { join as join5, dirname as dirname3, posix } from "path";
|
|
3477
4142
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3478
4143
|
var REGISTRY_URL = "https://registry.npmjs.org/github-search-cli/latest";
|
|
4144
|
+
var PKG_NAME2 = "github-search-cli";
|
|
3479
4145
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3480
4146
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
3481
4147
|
var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
|
|
@@ -3560,30 +4226,55 @@ function suppressUpdateNotices() {
|
|
|
3560
4226
|
suppressed: true
|
|
3561
4227
|
});
|
|
3562
4228
|
}
|
|
3563
|
-
|
|
3564
|
-
const
|
|
3565
|
-
const
|
|
3566
|
-
|
|
4229
|
+
function detectInstallLocation(pkgDir) {
|
|
4230
|
+
const norm = pkgDir.split("\\").join("/");
|
|
4231
|
+
const nodeModules = posix.dirname(norm);
|
|
4232
|
+
if (posix.basename(nodeModules) !== "node_modules") return null;
|
|
4233
|
+
const owner = posix.dirname(nodeModules);
|
|
4234
|
+
if (/(^|\/)\.bun(\/|$)/.test(owner)) return { channel: "bun" };
|
|
4235
|
+
const prefix = posix.basename(owner) === "lib" ? posix.dirname(owner) : owner;
|
|
4236
|
+
return { channel: "npm", prefix };
|
|
4237
|
+
}
|
|
4238
|
+
function buildUpdateCommand(loc) {
|
|
4239
|
+
if (loc.channel === "bun") return ["bun", "install", "-g", PKG_NAME2];
|
|
4240
|
+
const args = ["npm", "install", "-g"];
|
|
4241
|
+
if (loc.prefix) args.push("--prefix", loc.prefix);
|
|
4242
|
+
args.push(PKG_NAME2);
|
|
4243
|
+
return args;
|
|
4244
|
+
}
|
|
4245
|
+
function quoteWin32(arg) {
|
|
4246
|
+
return /\s/.test(arg) ? `"${arg}"` : arg;
|
|
4247
|
+
}
|
|
4248
|
+
async function performUpdate(location = detectInstallLocation(PKG_DIR2)) {
|
|
4249
|
+
if (!location) {
|
|
4250
|
+
debugLog(
|
|
4251
|
+
"Update skipped: the running install is not under a node_modules tree (compiled binary or dev checkout). Update it manually."
|
|
4252
|
+
);
|
|
4253
|
+
return false;
|
|
4254
|
+
}
|
|
4255
|
+
const argv = buildUpdateCommand(location);
|
|
3567
4256
|
try {
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
return proc.exitCode === 0;
|
|
3579
|
-
}
|
|
3580
|
-
const { spawn: spawn3 } = await import("child_process");
|
|
3581
|
-
return await new Promise((resolve) => {
|
|
3582
|
-
const proc = spawn3(cmd[0], cmd.slice(1), { stdio: "ignore" });
|
|
3583
|
-
proc.on("close", (code) => resolve(code === 0));
|
|
3584
|
-
proc.on("error", () => resolve(false));
|
|
4257
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
4258
|
+
const useShell = process.platform === "win32";
|
|
4259
|
+
const proc = spawn4(
|
|
4260
|
+
argv[0],
|
|
4261
|
+
useShell ? argv.slice(1).map(quoteWin32) : argv.slice(1),
|
|
4262
|
+
{ stdio: ["ignore", "ignore", "pipe"], shell: useShell }
|
|
4263
|
+
);
|
|
4264
|
+
let stderr = "";
|
|
4265
|
+
proc.stderr?.on("data", (chunk) => {
|
|
4266
|
+
stderr += String(chunk);
|
|
3585
4267
|
});
|
|
3586
|
-
|
|
4268
|
+
const code = await new Promise((resolve) => {
|
|
4269
|
+
proc.on("close", (c) => resolve(c));
|
|
4270
|
+
proc.on("error", () => resolve(-1));
|
|
4271
|
+
});
|
|
4272
|
+
if (code !== 0) {
|
|
4273
|
+
debugLog(`Install failed (code ${code}): ${stderr.trim()}`);
|
|
4274
|
+
}
|
|
4275
|
+
return code === 0;
|
|
4276
|
+
} catch (e) {
|
|
4277
|
+
debugLog(`Update failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3587
4278
|
return false;
|
|
3588
4279
|
}
|
|
3589
4280
|
}
|
|
@@ -3603,7 +4294,7 @@ function fetchReleaseNotes(version) {
|
|
|
3603
4294
|
try {
|
|
3604
4295
|
const changelogPath = join5(PKG_DIR2, "CHANGELOG.md");
|
|
3605
4296
|
const changelog = readFileSync5(changelogPath, "utf-8");
|
|
3606
|
-
const header =
|
|
4297
|
+
const header = `## [${version}]`;
|
|
3607
4298
|
const start = changelog.indexOf(header);
|
|
3608
4299
|
if (start === -1) return null;
|
|
3609
4300
|
const bodyStart = start + header.length;
|
|
@@ -3617,7 +4308,7 @@ function fetchReleaseNotes(version) {
|
|
|
3617
4308
|
}
|
|
3618
4309
|
|
|
3619
4310
|
// src/landing.ts
|
|
3620
|
-
import { BoxRenderable as
|
|
4311
|
+
import { BoxRenderable as BoxRenderable4, TextRenderable as TextRenderable4 } from "@opentui/core";
|
|
3621
4312
|
function landingKeysActive(mode, overlay) {
|
|
3622
4313
|
return mode === "landing" && overlay === "none";
|
|
3623
4314
|
}
|
|
@@ -3747,15 +4438,15 @@ async function launchBrowser(theme_override) {
|
|
|
3747
4438
|
let currentPage = 1;
|
|
3748
4439
|
let totalCount = 0;
|
|
3749
4440
|
let deepDiveActive = false;
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
const header = new
|
|
4441
|
+
const compareList = [];
|
|
4442
|
+
const _quitArmed = false;
|
|
4443
|
+
const header = new TextRenderable5(renderer, {
|
|
3753
4444
|
content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
|
|
3754
4445
|
bg: colors.bg,
|
|
3755
4446
|
fg: colors.muted,
|
|
3756
4447
|
height: 1
|
|
3757
4448
|
});
|
|
3758
|
-
const trendingTabBox = new
|
|
4449
|
+
const trendingTabBox = new BoxRenderable5(renderer, {
|
|
3759
4450
|
flexDirection: "row",
|
|
3760
4451
|
height: 1,
|
|
3761
4452
|
backgroundColor: colors.surface,
|
|
@@ -3770,7 +4461,7 @@ async function launchBrowser(theme_override) {
|
|
|
3770
4461
|
TAB_NAMES.forEach((name, i) => {
|
|
3771
4462
|
const isActive = name === trendingTab;
|
|
3772
4463
|
const label = ` ${name} `;
|
|
3773
|
-
const tt = new
|
|
4464
|
+
const tt = new TextRenderable5(renderer, {
|
|
3774
4465
|
content: label,
|
|
3775
4466
|
fg: isActive ? colors.bg : colors.muted,
|
|
3776
4467
|
bg: isActive ? colors.blue : colors.bg,
|
|
@@ -3779,7 +4470,7 @@ async function launchBrowser(theme_override) {
|
|
|
3779
4470
|
trendingTabTexts.push(tt);
|
|
3780
4471
|
trendingTabBox.add(tt);
|
|
3781
4472
|
if (i < TAB_NAMES.length - 1) {
|
|
3782
|
-
const sp = new
|
|
4473
|
+
const sp = new TextRenderable5(renderer, {
|
|
3783
4474
|
content: " ",
|
|
3784
4475
|
fg: colors.muted,
|
|
3785
4476
|
bg: colors.bg,
|
|
@@ -3792,7 +4483,7 @@ async function launchBrowser(theme_override) {
|
|
|
3792
4483
|
}
|
|
3793
4484
|
root.add(header);
|
|
3794
4485
|
root.add(trendingTabBox);
|
|
3795
|
-
const searchBox = new
|
|
4486
|
+
const searchBox = new BoxRenderable5(renderer, {
|
|
3796
4487
|
visible: true,
|
|
3797
4488
|
width: "100%",
|
|
3798
4489
|
flexDirection: "row",
|
|
@@ -3811,7 +4502,7 @@ async function launchBrowser(theme_override) {
|
|
|
3811
4502
|
});
|
|
3812
4503
|
searchBox.add(searchInput);
|
|
3813
4504
|
root.add(searchBox);
|
|
3814
|
-
const toolbarText = new
|
|
4505
|
+
const toolbarText = new TextRenderable5(renderer, {
|
|
3815
4506
|
content: formatToolbar(currentSort, currentLimit, totalCount),
|
|
3816
4507
|
visible: true,
|
|
3817
4508
|
// hidden in trending mode
|
|
@@ -3820,14 +4511,14 @@ async function launchBrowser(theme_override) {
|
|
|
3820
4511
|
paddingX: 1
|
|
3821
4512
|
});
|
|
3822
4513
|
root.add(toolbarText);
|
|
3823
|
-
const body = new
|
|
4514
|
+
const body = new BoxRenderable5(renderer, {
|
|
3824
4515
|
flexDirection: "row",
|
|
3825
4516
|
flexGrow: 1,
|
|
3826
4517
|
gap: 1,
|
|
3827
4518
|
paddingY: 1,
|
|
3828
4519
|
paddingX: 1
|
|
3829
4520
|
});
|
|
3830
|
-
const resultsBox = new
|
|
4521
|
+
const resultsBox = new BoxRenderable5(renderer, {
|
|
3831
4522
|
border: true,
|
|
3832
4523
|
borderColor: colors.border,
|
|
3833
4524
|
title: " Results ",
|
|
@@ -3856,7 +4547,7 @@ async function launchBrowser(theme_override) {
|
|
|
3856
4547
|
itemSpacing: 0
|
|
3857
4548
|
});
|
|
3858
4549
|
resultsBox.add(resultsSelect);
|
|
3859
|
-
const detailBox = new
|
|
4550
|
+
const detailBox = new BoxRenderable5(renderer, {
|
|
3860
4551
|
width: "50%",
|
|
3861
4552
|
flexDirection: "column",
|
|
3862
4553
|
paddingX: 1,
|
|
@@ -3864,7 +4555,7 @@ async function launchBrowser(theme_override) {
|
|
|
3864
4555
|
focusable: false,
|
|
3865
4556
|
backgroundColor: colors.surface
|
|
3866
4557
|
});
|
|
3867
|
-
const detailText = new
|
|
4558
|
+
const detailText = new TextRenderable5(renderer, {
|
|
3868
4559
|
content: "",
|
|
3869
4560
|
fg: colors.text,
|
|
3870
4561
|
wrapMode: "none"
|
|
@@ -3873,7 +4564,7 @@ async function launchBrowser(theme_override) {
|
|
|
3873
4564
|
body.add(resultsBox);
|
|
3874
4565
|
body.add(detailBox);
|
|
3875
4566
|
root.add(body);
|
|
3876
|
-
const landingBox = new
|
|
4567
|
+
const landingBox = new BoxRenderable5(renderer, {
|
|
3877
4568
|
visible: false,
|
|
3878
4569
|
flexGrow: 1,
|
|
3879
4570
|
flexDirection: "column",
|
|
@@ -3881,7 +4572,7 @@ async function launchBrowser(theme_override) {
|
|
|
3881
4572
|
justifyContent: "center"
|
|
3882
4573
|
});
|
|
3883
4574
|
root.add(landingBox);
|
|
3884
|
-
const landingBanner = new
|
|
4575
|
+
const landingBanner = new TextRenderable5(renderer, {
|
|
3885
4576
|
content: [
|
|
3886
4577
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
3887
4578
|
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
@@ -3931,7 +4622,7 @@ async function launchBrowser(theme_override) {
|
|
|
3931
4622
|
const landingCards = [];
|
|
3932
4623
|
for (let i = 0; i < landingOptions.length; i++) {
|
|
3933
4624
|
const opt = landingOptions[i];
|
|
3934
|
-
const card = new
|
|
4625
|
+
const card = new BoxRenderable5(renderer, {
|
|
3935
4626
|
flexDirection: "column",
|
|
3936
4627
|
height: 5,
|
|
3937
4628
|
width: "60%",
|
|
@@ -3943,18 +4634,18 @@ async function launchBrowser(theme_override) {
|
|
|
3943
4634
|
});
|
|
3944
4635
|
landingCards.push(card);
|
|
3945
4636
|
landingBox.add(card);
|
|
3946
|
-
const spacerTop = new
|
|
3947
|
-
const titleLine = new
|
|
4637
|
+
const spacerTop = new TextRenderable5(renderer, { content: "", height: 1 });
|
|
4638
|
+
const titleLine = new TextRenderable5(renderer, {
|
|
3948
4639
|
content: opt.title,
|
|
3949
4640
|
fg: i === 0 ? colors.accent : colors.text,
|
|
3950
4641
|
height: 1
|
|
3951
4642
|
});
|
|
3952
|
-
const spacerBot = new
|
|
4643
|
+
const spacerBot = new TextRenderable5(renderer, { content: "", height: 1 });
|
|
3953
4644
|
card.add(spacerTop);
|
|
3954
4645
|
card.add(titleLine);
|
|
3955
4646
|
card.add(spacerBot);
|
|
3956
4647
|
}
|
|
3957
|
-
const landingHint = new
|
|
4648
|
+
const landingHint = new TextRenderable5(renderer, {
|
|
3958
4649
|
content: " \u2191\u2193 Navigate \u21E9 Select [b]ookmarks [?]help [q]uit",
|
|
3959
4650
|
fg: colors.muted,
|
|
3960
4651
|
bg: colors.bg,
|
|
@@ -4097,7 +4788,7 @@ async function launchBrowser(theme_override) {
|
|
|
4097
4788
|
renderer.requestRender();
|
|
4098
4789
|
}
|
|
4099
4790
|
const helpSections = buildHelpSections();
|
|
4100
|
-
const helpBox = new
|
|
4791
|
+
const helpBox = new BoxRenderable5(renderer, {
|
|
4101
4792
|
visible: false,
|
|
4102
4793
|
flexGrow: 1,
|
|
4103
4794
|
backgroundColor: colors.bg
|
|
@@ -4119,7 +4810,7 @@ async function launchBrowser(theme_override) {
|
|
|
4119
4810
|
}
|
|
4120
4811
|
});
|
|
4121
4812
|
for (const section of helpSections) {
|
|
4122
|
-
const sectionBox = new
|
|
4813
|
+
const sectionBox = new BoxRenderable5(renderer, {
|
|
4123
4814
|
title: section.title,
|
|
4124
4815
|
titleColor: colors[section.titleColor] ?? section.titleColor,
|
|
4125
4816
|
flexDirection: "column",
|
|
@@ -4131,7 +4822,7 @@ async function launchBrowser(theme_override) {
|
|
|
4131
4822
|
for (const row of section.rows) {
|
|
4132
4823
|
const keys = row.keys.padEnd(HELP_KEYS_COLUMN);
|
|
4133
4824
|
sectionBox.add(
|
|
4134
|
-
new
|
|
4825
|
+
new TextRenderable5(renderer, {
|
|
4135
4826
|
content: ` ${keys} ${row.action}`,
|
|
4136
4827
|
fg: colors.text
|
|
4137
4828
|
})
|
|
@@ -4139,7 +4830,7 @@ async function launchBrowser(theme_override) {
|
|
|
4139
4830
|
}
|
|
4140
4831
|
if (section.note) {
|
|
4141
4832
|
sectionBox.add(
|
|
4142
|
-
new
|
|
4833
|
+
new TextRenderable5(renderer, {
|
|
4143
4834
|
content: ` ${section.note}`,
|
|
4144
4835
|
fg: colors.muted
|
|
4145
4836
|
})
|
|
@@ -4149,7 +4840,7 @@ async function launchBrowser(theme_override) {
|
|
|
4149
4840
|
}
|
|
4150
4841
|
helpBox.add(helpScroll);
|
|
4151
4842
|
root.add(helpBox);
|
|
4152
|
-
const historyBox = new
|
|
4843
|
+
const historyBox = new BoxRenderable5(renderer, {
|
|
4153
4844
|
visible: false,
|
|
4154
4845
|
flexGrow: 1,
|
|
4155
4846
|
backgroundColor: colors.bg,
|
|
@@ -4184,7 +4875,7 @@ async function launchBrowser(theme_override) {
|
|
|
4184
4875
|
}
|
|
4185
4876
|
historySelect.setSelectedIndex(0);
|
|
4186
4877
|
}
|
|
4187
|
-
const bookmarksBox = new
|
|
4878
|
+
const bookmarksBox = new BoxRenderable5(renderer, {
|
|
4188
4879
|
visible: false,
|
|
4189
4880
|
flexGrow: 1,
|
|
4190
4881
|
backgroundColor: colors.bg,
|
|
@@ -4219,7 +4910,7 @@ async function launchBrowser(theme_override) {
|
|
|
4219
4910
|
}
|
|
4220
4911
|
bookmarksSelect.setSelectedIndex(0);
|
|
4221
4912
|
}
|
|
4222
|
-
const savedBox = new
|
|
4913
|
+
const savedBox = new BoxRenderable5(renderer, {
|
|
4223
4914
|
visible: false,
|
|
4224
4915
|
flexGrow: 1,
|
|
4225
4916
|
backgroundColor: colors.bg,
|
|
@@ -4239,7 +4930,7 @@ async function launchBrowser(theme_override) {
|
|
|
4239
4930
|
});
|
|
4240
4931
|
savedBox.add(savedSelect);
|
|
4241
4932
|
root.add(savedBox);
|
|
4242
|
-
const topicsBox = new
|
|
4933
|
+
const topicsBox = new BoxRenderable5(renderer, {
|
|
4243
4934
|
visible: false,
|
|
4244
4935
|
flexGrow: 1,
|
|
4245
4936
|
backgroundColor: colors.bg,
|
|
@@ -4274,7 +4965,7 @@ async function launchBrowser(theme_override) {
|
|
|
4274
4965
|
}
|
|
4275
4966
|
topicsSelect.setSelectedIndex(0);
|
|
4276
4967
|
}
|
|
4277
|
-
const exportBox = new
|
|
4968
|
+
const exportBox = new BoxRenderable5(renderer, {
|
|
4278
4969
|
visible: false,
|
|
4279
4970
|
flexGrow: 1,
|
|
4280
4971
|
backgroundColor: colors.bg,
|
|
@@ -4311,28 +5002,28 @@ async function launchBrowser(theme_override) {
|
|
|
4311
5002
|
});
|
|
4312
5003
|
exportBox.add(exportSelect);
|
|
4313
5004
|
root.add(exportBox);
|
|
4314
|
-
const compareBox = new
|
|
5005
|
+
const compareBox = new BoxRenderable5(renderer, {
|
|
4315
5006
|
visible: false,
|
|
4316
5007
|
flexGrow: 1,
|
|
4317
5008
|
backgroundColor: colors.bg,
|
|
4318
5009
|
title: " Comparison ",
|
|
4319
5010
|
titleColor: colors.yellow
|
|
4320
5011
|
});
|
|
4321
|
-
const compareText = new
|
|
5012
|
+
const compareText = new TextRenderable5(renderer, {
|
|
4322
5013
|
content: "",
|
|
4323
5014
|
fg: colors.text,
|
|
4324
5015
|
bg: colors.bg
|
|
4325
5016
|
});
|
|
4326
5017
|
compareBox.add(compareText);
|
|
4327
5018
|
root.add(compareBox);
|
|
4328
|
-
const notifsBox = new
|
|
5019
|
+
const notifsBox = new BoxRenderable5(renderer, {
|
|
4329
5020
|
visible: false,
|
|
4330
5021
|
flexGrow: 1,
|
|
4331
5022
|
backgroundColor: colors.bg,
|
|
4332
5023
|
title: " Notifications ",
|
|
4333
5024
|
titleColor: colors.yellow
|
|
4334
5025
|
});
|
|
4335
|
-
const notifsText = new
|
|
5026
|
+
const notifsText = new TextRenderable5(renderer, {
|
|
4336
5027
|
content: "(no notifications)",
|
|
4337
5028
|
fg: colors.text,
|
|
4338
5029
|
bg: colors.bg
|
|
@@ -4347,7 +5038,7 @@ async function launchBrowser(theme_override) {
|
|
|
4347
5038
|
notifsText.content = notifs2.map((n) => ` ${n.icon} ${n.message}`).join("\n");
|
|
4348
5039
|
}
|
|
4349
5040
|
}
|
|
4350
|
-
const shareBox = new
|
|
5041
|
+
const shareBox = new BoxRenderable5(renderer, {
|
|
4351
5042
|
visible: false,
|
|
4352
5043
|
flexGrow: 1,
|
|
4353
5044
|
backgroundColor: colors.bg,
|
|
@@ -4406,7 +5097,7 @@ async function launchBrowser(theme_override) {
|
|
|
4406
5097
|
}
|
|
4407
5098
|
savedSelect.setSelectedIndex(0);
|
|
4408
5099
|
}
|
|
4409
|
-
const leaderDim = new
|
|
5100
|
+
const leaderDim = new BoxRenderable5(renderer, {
|
|
4410
5101
|
visible: false,
|
|
4411
5102
|
position: "absolute",
|
|
4412
5103
|
top: 0,
|
|
@@ -4417,7 +5108,7 @@ async function launchBrowser(theme_override) {
|
|
|
4417
5108
|
// 60% opacity black (hex alpha: 0x99 ≈ 153/255)
|
|
4418
5109
|
});
|
|
4419
5110
|
root.add(leaderDim);
|
|
4420
|
-
const leaderBox = new
|
|
5111
|
+
const leaderBox = new BoxRenderable5(renderer, {
|
|
4421
5112
|
visible: false,
|
|
4422
5113
|
position: "absolute",
|
|
4423
5114
|
width: "80%",
|
|
@@ -4449,7 +5140,7 @@ async function launchBrowser(theme_override) {
|
|
|
4449
5140
|
selectedDescriptionColor: colors.selectionText,
|
|
4450
5141
|
itemSpacing: 0
|
|
4451
5142
|
});
|
|
4452
|
-
const leaderFooter = new
|
|
5143
|
+
const leaderFooter = new TextRenderable5(renderer, {
|
|
4453
5144
|
content: " \u2191\u2193 select Enter run Esc/q close ",
|
|
4454
5145
|
fg: colors.muted,
|
|
4455
5146
|
bg: colors.surfaceDim,
|
|
@@ -4460,7 +5151,7 @@ async function launchBrowser(theme_override) {
|
|
|
4460
5151
|
leaderBox.add(leaderFooter);
|
|
4461
5152
|
root.add(leaderDim);
|
|
4462
5153
|
root.add(leaderBox);
|
|
4463
|
-
const updateDim = new
|
|
5154
|
+
const updateDim = new BoxRenderable5(renderer, {
|
|
4464
5155
|
visible: false,
|
|
4465
5156
|
position: "absolute",
|
|
4466
5157
|
top: 0,
|
|
@@ -4470,7 +5161,7 @@ async function launchBrowser(theme_override) {
|
|
|
4470
5161
|
backgroundColor: "#00000088"
|
|
4471
5162
|
});
|
|
4472
5163
|
root.add(updateDim);
|
|
4473
|
-
const updateBox = new
|
|
5164
|
+
const updateBox = new BoxRenderable5(renderer, {
|
|
4474
5165
|
visible: false,
|
|
4475
5166
|
position: "absolute",
|
|
4476
5167
|
width: "55%",
|
|
@@ -4489,11 +5180,19 @@ async function launchBrowser(theme_override) {
|
|
|
4489
5180
|
paddingLeft: 2,
|
|
4490
5181
|
paddingRight: 2
|
|
4491
5182
|
});
|
|
4492
|
-
const
|
|
5183
|
+
const updateHeader = new TextRenderable5(renderer, {
|
|
4493
5184
|
content: "",
|
|
4494
5185
|
fg: colors.accent,
|
|
4495
5186
|
bg: colors.surfaceDim
|
|
4496
5187
|
});
|
|
5188
|
+
const updateNotes = createMarkdownView({
|
|
5189
|
+
renderer,
|
|
5190
|
+
colors,
|
|
5191
|
+
// Notes ship with the release and are mostly prose/lists; skip the
|
|
5192
|
+
// image pipeline in this narrow panel.
|
|
5193
|
+
images: false,
|
|
5194
|
+
background: colors.surfaceDim
|
|
5195
|
+
});
|
|
4497
5196
|
const updateScroll = new ScrollBoxRenderable3(renderer, {
|
|
4498
5197
|
flexGrow: 1,
|
|
4499
5198
|
backgroundColor: colors.surfaceDim,
|
|
@@ -4513,14 +5212,14 @@ async function launchBrowser(theme_override) {
|
|
|
4513
5212
|
}
|
|
4514
5213
|
});
|
|
4515
5214
|
let updateSelectedOption = 0;
|
|
4516
|
-
const updateOptionsRow = new
|
|
5215
|
+
const updateOptionsRow = new BoxRenderable5(renderer, {
|
|
4517
5216
|
flexDirection: "row",
|
|
4518
5217
|
gap: 1,
|
|
4519
5218
|
backgroundColor: colors.surfaceDim,
|
|
4520
5219
|
height: 3,
|
|
4521
5220
|
width: "100%"
|
|
4522
5221
|
});
|
|
4523
|
-
const updateNowBox = new
|
|
5222
|
+
const updateNowBox = new BoxRenderable5(renderer, {
|
|
4524
5223
|
border: true,
|
|
4525
5224
|
borderColor: updateSelectedOption === 0 ? colors.yellow : colors.border,
|
|
4526
5225
|
backgroundColor: colors.surfaceDim,
|
|
@@ -4529,13 +5228,13 @@ async function launchBrowser(theme_override) {
|
|
|
4529
5228
|
paddingLeft: 1,
|
|
4530
5229
|
paddingRight: 1
|
|
4531
5230
|
});
|
|
4532
|
-
const updateNowText = new
|
|
5231
|
+
const updateNowText = new TextRenderable5(renderer, {
|
|
4533
5232
|
content: " Update Now ",
|
|
4534
5233
|
fg: updateSelectedOption === 0 ? colors.yellow : colors.text,
|
|
4535
5234
|
bg: colors.surfaceDim
|
|
4536
5235
|
});
|
|
4537
5236
|
updateNowBox.add(updateNowText);
|
|
4538
|
-
const laterBox = new
|
|
5237
|
+
const laterBox = new BoxRenderable5(renderer, {
|
|
4539
5238
|
border: true,
|
|
4540
5239
|
borderColor: updateSelectedOption === 1 ? colors.yellow : colors.border,
|
|
4541
5240
|
backgroundColor: colors.surfaceDim,
|
|
@@ -4544,13 +5243,13 @@ async function launchBrowser(theme_override) {
|
|
|
4544
5243
|
paddingLeft: 1,
|
|
4545
5244
|
paddingRight: 1
|
|
4546
5245
|
});
|
|
4547
|
-
const laterText = new
|
|
5246
|
+
const laterText = new TextRenderable5(renderer, {
|
|
4548
5247
|
content: " Later",
|
|
4549
5248
|
fg: updateSelectedOption === 1 ? colors.yellow : colors.text,
|
|
4550
5249
|
bg: colors.surfaceDim
|
|
4551
5250
|
});
|
|
4552
5251
|
laterBox.add(laterText);
|
|
4553
|
-
const neverBox = new
|
|
5252
|
+
const neverBox = new BoxRenderable5(renderer, {
|
|
4554
5253
|
border: true,
|
|
4555
5254
|
borderColor: updateSelectedOption === 2 ? colors.yellow : colors.border,
|
|
4556
5255
|
backgroundColor: colors.surfaceDim,
|
|
@@ -4559,7 +5258,7 @@ async function launchBrowser(theme_override) {
|
|
|
4559
5258
|
paddingLeft: 1,
|
|
4560
5259
|
paddingRight: 1
|
|
4561
5260
|
});
|
|
4562
|
-
const neverText = new
|
|
5261
|
+
const neverText = new TextRenderable5(renderer, {
|
|
4563
5262
|
content: " Don't show again",
|
|
4564
5263
|
fg: updateSelectedOption === 2 ? colors.yellow : colors.text,
|
|
4565
5264
|
bg: colors.surfaceDim
|
|
@@ -4576,7 +5275,7 @@ async function launchBrowser(theme_override) {
|
|
|
4576
5275
|
}
|
|
4577
5276
|
updateOptionsRow.add(updateNowBox);
|
|
4578
5277
|
updateOptionsRow.add(laterBox);
|
|
4579
|
-
const updateFooter = new
|
|
5278
|
+
const updateFooter = new TextRenderable5(renderer, {
|
|
4580
5279
|
content: " \u2191\u2193/jk/PgUp/PgDn scroll \u2190\u2192 select Enter Esc/q close",
|
|
4581
5280
|
fg: colors.muted,
|
|
4582
5281
|
bg: colors.surfaceDim,
|
|
@@ -4584,7 +5283,8 @@ async function launchBrowser(theme_override) {
|
|
|
4584
5283
|
paddingX: 1
|
|
4585
5284
|
});
|
|
4586
5285
|
updateBox.add(updateScroll);
|
|
4587
|
-
updateScroll.add(
|
|
5286
|
+
updateScroll.add(updateHeader);
|
|
5287
|
+
updateScroll.add(updateNotes.renderable);
|
|
4588
5288
|
updateBox.add(updateOptionsRow);
|
|
4589
5289
|
updateBox.add(updateFooter);
|
|
4590
5290
|
root.add(updateDim);
|
|
@@ -4611,6 +5311,7 @@ async function launchBrowser(theme_override) {
|
|
|
4611
5311
|
if (menuStack.length <= 1) return false;
|
|
4612
5312
|
const prev = menuStack[menuStack.length - 2];
|
|
4613
5313
|
const current = menuStack.pop();
|
|
5314
|
+
if (!current) return false;
|
|
4614
5315
|
leaderSelect.options = prev.entries.map((e) => ({
|
|
4615
5316
|
name: e.type === "category" ? ` ${e.icon} ${e.name} \u2192` : ` ${e.name}`,
|
|
4616
5317
|
description: e.description,
|
|
@@ -4927,19 +5628,16 @@ async function launchBrowser(theme_override) {
|
|
|
4927
5628
|
if (!cachedVersion) {
|
|
4928
5629
|
cachedVersion = getVersion();
|
|
4929
5630
|
}
|
|
4930
|
-
const
|
|
5631
|
+
const currentVersion = cachedVersion;
|
|
4931
5632
|
if (!state.lastInstalledVersion) return;
|
|
4932
|
-
if (state.lastInstalledVersion ===
|
|
4933
|
-
const notes = fetchReleaseNotes(
|
|
4934
|
-
const notesWidth = Math.max(
|
|
4935
|
-
30,
|
|
4936
|
-
Math.floor((process.stdout.columns || 80) * 0.55) - 8
|
|
4937
|
-
);
|
|
4938
|
-
const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : " No release notes found for this version.";
|
|
5633
|
+
if (state.lastInstalledVersion === currentVersion) return;
|
|
5634
|
+
const notes = fetchReleaseNotes(currentVersion);
|
|
4939
5635
|
updateSelectedOption = 0;
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5636
|
+
updateHeader.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
|
|
5637
|
+
`;
|
|
5638
|
+
updateNotes.setContent(
|
|
5639
|
+
notes ?? "_No release notes found for this version._"
|
|
5640
|
+
);
|
|
4943
5641
|
renderUpdateOptions();
|
|
4944
5642
|
showOverlay("update");
|
|
4945
5643
|
}
|
|
@@ -4948,32 +5646,22 @@ ${notesBlock}`;
|
|
|
4948
5646
|
if (cachedVersion === null) {
|
|
4949
5647
|
cachedVersion = getVersion();
|
|
4950
5648
|
}
|
|
4951
|
-
const
|
|
4952
|
-
const latest = process.env.DEBUG_FORCE_UPDATE || await checkForUpdate(
|
|
5649
|
+
const currentVersion = cachedVersion;
|
|
5650
|
+
const latest = process.env.DEBUG_FORCE_UPDATE || await checkForUpdate(currentVersion);
|
|
4953
5651
|
markUpdateChecked(latest ?? void 0);
|
|
4954
5652
|
if (latest) {
|
|
4955
5653
|
updateSelectedOption = 0;
|
|
4956
5654
|
const notes = fetchReleaseNotes(latest);
|
|
4957
|
-
|
|
4958
|
-
30,
|
|
4959
|
-
Math.floor((process.stdout.columns || 80) * 0.55) - 8
|
|
4960
|
-
);
|
|
4961
|
-
const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : ` A new version is available
|
|
4962
|
-
|
|
4963
|
-
Current: ${currentVersion2}
|
|
4964
|
-
Latest: ${latest}
|
|
5655
|
+
updateHeader.content = ` A new version is available
|
|
4965
5656
|
|
|
4966
|
-
|
|
4967
|
-
updateText.content = ` A new version is available
|
|
4968
|
-
|
|
4969
|
-
Current: ${currentVersion2}
|
|
5657
|
+
Current: ${currentVersion}
|
|
4970
5658
|
Latest: ${latest}
|
|
4971
|
-
|
|
4972
|
-
|
|
5659
|
+
`;
|
|
5660
|
+
updateNotes.setContent(notes ?? "Run `npm install -g ghfind` to update.");
|
|
4973
5661
|
renderUpdateOptions();
|
|
4974
5662
|
showOverlay("update");
|
|
4975
5663
|
} else {
|
|
4976
|
-
debugLog(`No update available (current: ${
|
|
5664
|
+
debugLog(`No update available (current: ${currentVersion})`);
|
|
4977
5665
|
}
|
|
4978
5666
|
}
|
|
4979
5667
|
function openUrlIfSelected() {
|
|
@@ -5018,7 +5706,7 @@ ${notesBlock}`;
|
|
|
5018
5706
|
}
|
|
5019
5707
|
function saveCurrentSearch() {
|
|
5020
5708
|
if (currentQueryInput) {
|
|
5021
|
-
const name = currentQueryInput.length > 40 ? currentQueryInput.slice(0, 37)
|
|
5709
|
+
const name = currentQueryInput.length > 40 ? `${currentQueryInput.slice(0, 37)}...` : currentQueryInput;
|
|
5022
5710
|
saveSearch(
|
|
5023
5711
|
name,
|
|
5024
5712
|
currentQueryInput,
|
|
@@ -5052,7 +5740,7 @@ ${notesBlock}`;
|
|
|
5052
5740
|
renderer.requestRender();
|
|
5053
5741
|
}
|
|
5054
5742
|
}
|
|
5055
|
-
const orgBox = new
|
|
5743
|
+
const orgBox = new BoxRenderable5(renderer, {
|
|
5056
5744
|
visible: false,
|
|
5057
5745
|
flexGrow: 1,
|
|
5058
5746
|
backgroundColor: colors.surface,
|
|
@@ -5075,14 +5763,14 @@ ${notesBlock}`;
|
|
|
5075
5763
|
}
|
|
5076
5764
|
}
|
|
5077
5765
|
});
|
|
5078
|
-
const orgText = new
|
|
5766
|
+
const orgText = new TextRenderable5(renderer, {
|
|
5079
5767
|
content: "",
|
|
5080
5768
|
fg: colors.text,
|
|
5081
5769
|
bg: colors.bg
|
|
5082
5770
|
});
|
|
5083
5771
|
orgScroll.add(orgText);
|
|
5084
5772
|
orgBox.add(orgScroll);
|
|
5085
|
-
const orgFooter = new
|
|
5773
|
+
const orgFooter = new TextRenderable5(renderer, {
|
|
5086
5774
|
content: " \u2191\u2193/jk scroll Esc/q close",
|
|
5087
5775
|
fg: colors.muted,
|
|
5088
5776
|
bg: colors.bg,
|
|
@@ -5148,7 +5836,7 @@ ${notesBlock}`;
|
|
|
5148
5836
|
}
|
|
5149
5837
|
renderer.requestRender();
|
|
5150
5838
|
}
|
|
5151
|
-
const readmeBox = new
|
|
5839
|
+
const readmeBox = new BoxRenderable5(renderer, {
|
|
5152
5840
|
visible: false,
|
|
5153
5841
|
flexGrow: 1,
|
|
5154
5842
|
backgroundColor: colors.surface,
|
|
@@ -5171,15 +5859,17 @@ ${notesBlock}`;
|
|
|
5171
5859
|
}
|
|
5172
5860
|
}
|
|
5173
5861
|
});
|
|
5174
|
-
const
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5862
|
+
const readmeView = createMarkdownView({
|
|
5863
|
+
renderer,
|
|
5864
|
+
colors,
|
|
5865
|
+
loadImage: (url) => fetchImageBytes(url, { token: githubToken }),
|
|
5866
|
+
getImageWidth: () => Math.max(24, (process.stdout.columns || 80) - 12)
|
|
5178
5867
|
});
|
|
5179
|
-
readmeScroll.add(
|
|
5868
|
+
readmeScroll.add(readmeView.renderable);
|
|
5180
5869
|
readmeBox.add(readmeScroll);
|
|
5181
|
-
const
|
|
5182
|
-
|
|
5870
|
+
const readmeFooterText = () => ` \u2191\u2193/jk scroll PgUp/PgDn page i images: ${readmeView.imagesEnabled ? "on" : "off"} Esc/q close`;
|
|
5871
|
+
const readmeFooter = new TextRenderable5(renderer, {
|
|
5872
|
+
content: readmeFooterText(),
|
|
5183
5873
|
fg: colors.muted,
|
|
5184
5874
|
bg: colors.bg,
|
|
5185
5875
|
height: 1,
|
|
@@ -5194,37 +5884,39 @@ ${notesBlock}`;
|
|
|
5194
5884
|
setStatus("No repo selected");
|
|
5195
5885
|
return;
|
|
5196
5886
|
}
|
|
5197
|
-
|
|
5887
|
+
readmeView.setContent(`Loading README for ${repo.fullName}\u2026`);
|
|
5888
|
+
readmeFooter.content = readmeFooterText();
|
|
5889
|
+
readmeScroll.scrollTop = 0;
|
|
5198
5890
|
showOverlay("readme");
|
|
5199
5891
|
try {
|
|
5200
5892
|
const headers = { "User-Agent": "ghfind/1.0" };
|
|
5201
5893
|
if (githubToken) headers.Authorization = `Bearer ${githubToken}`;
|
|
5202
5894
|
let text = "";
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5895
|
+
let sourceUrl = "";
|
|
5896
|
+
for (const [branch, file] of [
|
|
5897
|
+
["main", "README.md"],
|
|
5898
|
+
["master", "README.md"],
|
|
5899
|
+
["main", "README.rst"]
|
|
5207
5900
|
]) {
|
|
5208
|
-
const
|
|
5209
|
-
|
|
5210
|
-
});
|
|
5901
|
+
const url = readmeBaseUrl(repo.owner, repo.name, branch, file);
|
|
5902
|
+
const r = await fetch(url, { headers });
|
|
5211
5903
|
if (r.ok) {
|
|
5212
5904
|
text = await r.text();
|
|
5905
|
+
sourceUrl = url;
|
|
5213
5906
|
break;
|
|
5214
5907
|
}
|
|
5215
5908
|
}
|
|
5216
5909
|
if (!text) {
|
|
5217
|
-
|
|
5910
|
+
readmeView.setContent(`_(no README found for ${repo.fullName})_`);
|
|
5218
5911
|
} else {
|
|
5219
|
-
|
|
5220
|
-
readmeText.content = renderMarkdown(text, { width: termW });
|
|
5912
|
+
readmeView.setContent(text, sourceUrl);
|
|
5221
5913
|
}
|
|
5222
5914
|
} catch {
|
|
5223
|
-
|
|
5915
|
+
readmeView.setContent("_Failed to load README._");
|
|
5224
5916
|
}
|
|
5225
5917
|
renderer.requestRender();
|
|
5226
5918
|
}
|
|
5227
|
-
const statusBar = new
|
|
5919
|
+
const statusBar = new TextRenderable5(renderer, {
|
|
5228
5920
|
content: " Ready. Press Enter to search.",
|
|
5229
5921
|
fg: colors.muted,
|
|
5230
5922
|
height: 1,
|
|
@@ -5235,13 +5927,11 @@ ${notesBlock}`;
|
|
|
5235
5927
|
statusBar.content = ` ${text}`;
|
|
5236
5928
|
renderer.requestRender();
|
|
5237
5929
|
});
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
}, 2e3);
|
|
5244
|
-
}
|
|
5930
|
+
setTimeout(() => {
|
|
5931
|
+
if (currentOverlay === "none") {
|
|
5932
|
+
setStatus(nextTip());
|
|
5933
|
+
}
|
|
5934
|
+
}, 2e3);
|
|
5245
5935
|
function setStatus(msg) {
|
|
5246
5936
|
statusMgr.set("idle", msg);
|
|
5247
5937
|
}
|
|
@@ -5361,7 +6051,7 @@ ${notesBlock}`;
|
|
|
5361
6051
|
if (idx >= 0) {
|
|
5362
6052
|
const val = labelVals[idx];
|
|
5363
6053
|
label = val >= 1e3 ? `${(val / 1e3).toFixed(1).replace(/\.0$/, "")}k` : String(val);
|
|
5364
|
-
label = label.padStart(5)
|
|
6054
|
+
label = `${label.padStart(5)} `;
|
|
5365
6055
|
}
|
|
5366
6056
|
lines.push(`${label}\u2506${chars.join("")}`);
|
|
5367
6057
|
}
|
|
@@ -5707,6 +6397,17 @@ ${notesBlock}`;
|
|
|
5707
6397
|
doSearch(searchInput.value);
|
|
5708
6398
|
}
|
|
5709
6399
|
});
|
|
6400
|
+
searchInput.on("input", () => {
|
|
6401
|
+
const v = searchInput.value;
|
|
6402
|
+
if (v.length > 0) {
|
|
6403
|
+
const t3 = v.trim();
|
|
6404
|
+
if (t3.startsWith("{") && t3.endsWith("}") || t3.startsWith("[") && t3.endsWith("]")) {
|
|
6405
|
+
searchInput.value = "";
|
|
6406
|
+
setStatus("Ignored JSON paste \u2014 search queries accept plain text");
|
|
6407
|
+
renderer.requestRender();
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
});
|
|
5710
6411
|
resultsSelect.on("selectionChanged", () => {
|
|
5711
6412
|
const opt = resultsSelect.getSelectedOption();
|
|
5712
6413
|
if (currentMode === "packages") {
|
|
@@ -5789,13 +6490,22 @@ ${pack.description ?? ""}`;
|
|
|
5789
6490
|
return;
|
|
5790
6491
|
}
|
|
5791
6492
|
if (currentOverlay === "readme") {
|
|
6493
|
+
if (key.name === "i") {
|
|
6494
|
+
readmeView.setImagesEnabled(!readmeView.imagesEnabled);
|
|
6495
|
+
readmeFooter.content = readmeFooterText();
|
|
6496
|
+
setStatus(
|
|
6497
|
+
readmeView.imagesEnabled ? "README images on" : "README images off"
|
|
6498
|
+
);
|
|
6499
|
+
renderer.requestRender();
|
|
6500
|
+
}
|
|
5792
6501
|
return;
|
|
5793
6502
|
}
|
|
5794
6503
|
if (currentOverlay === "history") {
|
|
5795
6504
|
if (key.name === "d") {
|
|
5796
6505
|
const sel = historySelect.getSelectedOption();
|
|
5797
6506
|
if (sel?.value) {
|
|
5798
|
-
|
|
6507
|
+
const idx = sel.value.index;
|
|
6508
|
+
if (idx !== void 0) deleteHistoryEntry(idx);
|
|
5799
6509
|
refreshHistory();
|
|
5800
6510
|
setStatus("Deleted history entry");
|
|
5801
6511
|
}
|
|
@@ -5814,6 +6524,7 @@ ${pack.description ?? ""}`;
|
|
|
5814
6524
|
if (sel?.value) {
|
|
5815
6525
|
const entry = sel.value.entry;
|
|
5816
6526
|
showOverlay("none");
|
|
6527
|
+
if (!entry) return;
|
|
5817
6528
|
if (entry.mode === "trending") {
|
|
5818
6529
|
if (entry.tab)
|
|
5819
6530
|
trendingTab = entry.tab;
|
|
@@ -5832,7 +6543,9 @@ ${pack.description ?? ""}`;
|
|
|
5832
6543
|
if (key.name === "d") {
|
|
5833
6544
|
const sel = bookmarksSelect.getSelectedOption();
|
|
5834
6545
|
if (sel?.value) {
|
|
5835
|
-
removeBookmark(
|
|
6546
|
+
removeBookmark(
|
|
6547
|
+
sel.value.repo?.fullName ?? ""
|
|
6548
|
+
);
|
|
5836
6549
|
refreshBookmarks();
|
|
5837
6550
|
setStatus("Bookmark removed");
|
|
5838
6551
|
}
|
|
@@ -5841,7 +6554,8 @@ ${pack.description ?? ""}`;
|
|
|
5841
6554
|
}
|
|
5842
6555
|
if (key.name === "enter" || key.name === "return") {
|
|
5843
6556
|
const sel = bookmarksSelect.getSelectedOption();
|
|
5844
|
-
if (sel?.value)
|
|
6557
|
+
if (sel?.value)
|
|
6558
|
+
openUrl(sel.value.repo?.url ?? "");
|
|
5845
6559
|
return;
|
|
5846
6560
|
}
|
|
5847
6561
|
return;
|
|
@@ -5850,7 +6564,7 @@ ${pack.description ?? ""}`;
|
|
|
5850
6564
|
if (key.name === "d") {
|
|
5851
6565
|
const sel = savedSelect.getSelectedOption();
|
|
5852
6566
|
if (sel?.value) {
|
|
5853
|
-
deleteSavedSearch(sel.value.name);
|
|
6567
|
+
deleteSavedSearch(sel.value.name ?? "");
|
|
5854
6568
|
refreshSavedSearches();
|
|
5855
6569
|
setStatus("Saved search deleted");
|
|
5856
6570
|
}
|
|
@@ -5885,7 +6599,7 @@ ${pack.description ?? ""}`;
|
|
|
5885
6599
|
if (sel?.value) {
|
|
5886
6600
|
const topic = sel.value;
|
|
5887
6601
|
showOverlay("none");
|
|
5888
|
-
searchInput.value = `topic:${topic.name}`;
|
|
6602
|
+
searchInput.value = `topic:${topic.name ?? ""}`;
|
|
5889
6603
|
showSearchMode();
|
|
5890
6604
|
doSearch(`topic:${topic.name}`);
|
|
5891
6605
|
}
|
|
@@ -6076,7 +6790,7 @@ ${pack.description ?? ""}`;
|
|
|
6076
6790
|
}
|
|
6077
6791
|
if (/^[1-5]$/.test(key.name)) {
|
|
6078
6792
|
if (currentMode === "trending") {
|
|
6079
|
-
const idx = parseInt(key.name) - 1;
|
|
6793
|
+
const idx = parseInt(key.name, 10) - 1;
|
|
6080
6794
|
if (idx >= 0 && idx < TAB_NAMES.length) {
|
|
6081
6795
|
trendingTab = TAB_NAMES[idx];
|
|
6082
6796
|
loadTrending();
|
|
@@ -6120,14 +6834,14 @@ ${pack.description ?? ""}`;
|
|
|
6120
6834
|
return;
|
|
6121
6835
|
}
|
|
6122
6836
|
});
|
|
6123
|
-
const
|
|
6124
|
-
if (!session
|
|
6837
|
+
const _currentVersion = getVersion();
|
|
6838
|
+
if (!session?.mode) {
|
|
6125
6839
|
showLanding(true);
|
|
6126
6840
|
}
|
|
6127
6841
|
checkForUpdateAndShow();
|
|
6128
6842
|
checkPostUpgradePanel();
|
|
6129
6843
|
renderer.start();
|
|
6130
|
-
if (session
|
|
6844
|
+
if (session?.mode) {
|
|
6131
6845
|
if (currentMode === "trending") {
|
|
6132
6846
|
loadTrending();
|
|
6133
6847
|
} else {
|
|
@@ -6801,7 +7515,8 @@ async function processServerLine(line, state, config) {
|
|
|
6801
7515
|
}
|
|
6802
7516
|
}
|
|
6803
7517
|
function writeStdout(line) {
|
|
6804
|
-
process.stdout.write(line
|
|
7518
|
+
process.stdout.write(`${line}
|
|
7519
|
+
`);
|
|
6805
7520
|
}
|
|
6806
7521
|
async function runMcpServer(options = {}) {
|
|
6807
7522
|
const state = {
|
|
@@ -6820,8 +7535,9 @@ async function runMcpServer(options = {}) {
|
|
|
6820
7535
|
let buffer = "";
|
|
6821
7536
|
process.stdin.on("data", (chunk) => {
|
|
6822
7537
|
buffer += chunk;
|
|
6823
|
-
|
|
6824
|
-
|
|
7538
|
+
for (; ; ) {
|
|
7539
|
+
const idx = buffer.indexOf("\n");
|
|
7540
|
+
if (idx < 0) break;
|
|
6825
7541
|
const line = buffer.slice(0, idx).trim();
|
|
6826
7542
|
buffer = buffer.slice(idx + 1);
|
|
6827
7543
|
if (!line) continue;
|
|
@@ -6879,7 +7595,7 @@ function parseArgs(args) {
|
|
|
6879
7595
|
doctor: false,
|
|
6880
7596
|
mcp: false
|
|
6881
7597
|
};
|
|
6882
|
-
|
|
7598
|
+
const queryParts = [];
|
|
6883
7599
|
for (let i = 0; i < args.length; i++) {
|
|
6884
7600
|
const arg = args[i];
|
|
6885
7601
|
switch (arg) {
|
|
@@ -6933,8 +7649,8 @@ function parseArgs(args) {
|
|
|
6933
7649
|
flags.login = true;
|
|
6934
7650
|
break;
|
|
6935
7651
|
case "--limit": {
|
|
6936
|
-
const parsed = parseInt(args[++i]);
|
|
6937
|
-
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
7652
|
+
const parsed = parseInt(args[++i], 10);
|
|
7653
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
6938
7654
|
console.error(`Invalid limit: must be 1-100. Using default 50.`);
|
|
6939
7655
|
flags.limit = 50;
|
|
6940
7656
|
} else {
|
|
@@ -7174,7 +7890,12 @@ function printAgentSkill(name) {
|
|
|
7174
7890
|
}
|
|
7175
7891
|
async function runUserProfile(flags, outputFormat) {
|
|
7176
7892
|
try {
|
|
7177
|
-
const
|
|
7893
|
+
const user = flags.user ?? "";
|
|
7894
|
+
if (!user) {
|
|
7895
|
+
console.error("Usage: ghfind user <name> [--json] [--token <t>]");
|
|
7896
|
+
process.exit(1);
|
|
7897
|
+
}
|
|
7898
|
+
const profile = await fetchUserProfile(user, {
|
|
7178
7899
|
token: flags.token,
|
|
7179
7900
|
limit: flags.limit
|
|
7180
7901
|
});
|
|
@@ -7196,7 +7917,12 @@ async function runUserProfile(flags, outputFormat) {
|
|
|
7196
7917
|
}
|
|
7197
7918
|
async function runOrgProfile(flags, outputFormat) {
|
|
7198
7919
|
try {
|
|
7199
|
-
const
|
|
7920
|
+
const org = flags.org ?? "";
|
|
7921
|
+
if (!org) {
|
|
7922
|
+
console.error("Usage: ghfind org <name> [--json] [--token <t>]");
|
|
7923
|
+
process.exit(1);
|
|
7924
|
+
}
|
|
7925
|
+
const profile = await fetchOrgProfile(org, {
|
|
7200
7926
|
token: flags.token,
|
|
7201
7927
|
limit: flags.limit
|
|
7202
7928
|
});
|
|
@@ -7327,7 +8053,7 @@ async function runReleases(flags, outputFormat) {
|
|
|
7327
8053
|
}
|
|
7328
8054
|
async function runNonInteractive(flags, outputFormat) {
|
|
7329
8055
|
const handlers = {
|
|
7330
|
-
trending: async (
|
|
8056
|
+
trending: async (_ctx) => {
|
|
7331
8057
|
const since = flags.since === "weekly" ? "weekly" : flags.since === "monthly" ? "monthly" : "daily";
|
|
7332
8058
|
const trendingSearch = createTrendingSearch();
|
|
7333
8059
|
let language;
|
|
@@ -7398,7 +8124,7 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
7398
8124
|
token: flags.token
|
|
7399
8125
|
});
|
|
7400
8126
|
const repos = rankRepos(response.repos, flags.sort);
|
|
7401
|
-
await pipeExec(repos, flags.pipe);
|
|
8127
|
+
if (flags.pipe) await pipeExec(repos, flags.pipe);
|
|
7402
8128
|
},
|
|
7403
8129
|
format: async (ctx2) => {
|
|
7404
8130
|
const response = await ctx2.provider.search(ctx2.parsed, {
|