repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
pi/agent/skillz/github/github-cli/scripts/gh_code_search.mjs
Raw#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
function values(name, short) {
const out = [];
const a = process.argv;
for (let i = 2; i < a.length; i++)
if (a[i] === name || (short && a[i] === short)) out.push(a[++i]);
return out;
}
function arg(name, short, def) {
const v = values(name, short);
return v.length ? v.at(-1) : def;
}
function has(name) {
return process.argv.includes(name);
}
function positional() {
return process.argv
.slice(2)
.find(
(a, i, arr) =>
!a.startsWith("-") &&
![
"--limit",
"-L",
"--language",
"--filename",
"--extension",
"--repo",
"-R",
"--owner",
"--match",
"--size",
"--min-matches",
"--output",
"-o",
"--sort-by",
"--output-file",
"--timeout",
].includes(arr[i - 1]),
);
}
function checkGh() {
const r = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
return !r.error && r.status === 0;
}
function build(query) {
const cmd = ["gh", "search", "code", query];
const add = (flag, value) => {
if (value) cmd.push(flag, String(value));
};
add("--limit", arg("--limit", "-L", "30"));
add("--language", arg("--language"));
add("--filename", arg("--filename"));
add("--extension", arg("--extension"));
for (const r of values("--repo", "-R")) cmd.push("--repo", r);
for (const o of values("--owner")) cmd.push("--owner", o);
add("--match", arg("--match"));
add("--size", arg("--size"));
cmd.push("--json", "path,repository,sha,textMatches,url");
return cmd;
}
function execute(cmd, timeout) {
const r = spawnSync(cmd[0], cmd.slice(1), {
encoding: "utf8",
timeout: timeout * 1000,
maxBuffer: 64 * 1024 * 1024,
});
if (r.error?.code === "ETIMEDOUT")
throw new Error(
`Search timed out after ${timeout} seconds. Try a simpler query or increase --timeout.`,
);
if (r.status !== 0) {
const e = (r.stderr ?? "").trim();
if (e.includes("HTTP 403") && e.toLowerCase().includes("rate limit"))
throw new Error(
`GitHub API rate limit exceeded. Please wait and try again later.\nDetails: ${e}`,
);
if (e.includes("HTTP 408"))
throw new Error(
`Search query timed out. Try a simpler query or try again later.\nDetails: ${e}`,
);
throw new Error(`GitHub search failed: ${e}`);
}
return r.stdout.trim() ? JSON.parse(r.stdout) : [];
}
function filter(results) {
let out = results;
if (has("--exclude-forks")) out = out.filter((r) => !r.repository?.isFork);
if (has("--exclude-private"))
out = out.filter((r) => !r.repository?.isPrivate);
const min = Number(arg("--min-matches", undefined, "0"));
if (min) out = out.filter((r) => (r.textMatches ?? []).length >= min);
return out;
}
function sort(results, by) {
if (by === "matches")
return results.toSorted(
(a, b) => (b.textMatches ?? []).length - (a.textMatches ?? []).length,
);
if (by === "repo")
return results.toSorted((a, b) =>
(a.repository?.nameWithOwner ?? "").localeCompare(
b.repository?.nameWithOwner ?? "",
),
);
if (by === "path")
return results.toSorted((a, b) =>
(a.path ?? "").localeCompare(b.path ?? ""),
);
return results;
}
function pretty(results) {
if (!results.length) return "No results found.";
const out = [`Found ${results.length} result(s)\n`, "=".repeat(80)];
results.forEach((r, i) => {
let frag = r.textMatches?.[0]?.fragment ?? "";
if (frag.length > 100) frag = `${frag.slice(0, 97)}...`;
out.push(
`\n${i + 1}. ${r.repository?.nameWithOwner ?? "Unknown"}:${r.path ?? "Unknown"}`,
` URL: ${r.url ?? ""}`,
` Matches: ${(r.textMatches ?? []).length}`,
);
if (frag) out.push(` Preview: ${frag}`);
out.push("-".repeat(80));
});
return out.join("\n");
}
function summary(results) {
if (!results.length) return "No results found.";
const repos = {},
exts = {};
let total = 0;
for (const r of results) {
const repo = r.repository?.nameWithOwner ?? "Unknown";
repos[repo] = (repos[repo] ?? 0) + 1;
total += (r.textMatches ?? []).length;
const p = r.path ?? "";
if (p.includes(".")) {
const e = p.split(".").at(-1);
exts[e] = (exts[e] ?? 0) + 1;
}
}
const out = [
"SEARCH SUMMARY",
"=".repeat(80),
`Total files found: ${results.length}`,
`Total text matches: ${total}`,
`Unique repositories: ${Object.keys(repos).length}`,
"\nTop Repositories:",
];
for (const [k, v] of Object.entries(repos)
.sort((a, b) => b[1] - a[1])
.slice(0, 10))
out.push(` ${k}: ${v} file(s)`);
out.push("\nFile Extensions:");
for (const [k, v] of Object.entries(exts).sort((a, b) => b[1] - a[1]))
out.push(` .${k}: ${v} file(s)`);
return out.join("\n");
}
function main() {
if (!checkGh()) {
console.error(
"Error: GitHub CLI (gh) is not installed or not authenticated.",
);
console.error("Install from: https://cli.github.com/");
console.error("Then run: gh auth login");
return 1;
}
const q = positional();
if (!q) {
console.error("Error: query required");
return 1;
}
const results = sort(
filter(execute(build(q), Number(arg("--timeout", undefined, "30")))),
arg("--sort-by"),
);
const fmt = arg("--output", "-o", "pretty");
const output =
fmt === "json"
? JSON.stringify(results, null, 2)
: fmt === "summary"
? summary(results)
: pretty(results);
const file = arg("--output-file");
if (file) {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, output, "utf8");
console.error(`Results written to ${file}`);
} else console.log(output);
return 0;
}
export const __test = {
build,
filter,
positional,
pretty,
sort,
summary,
};
if (process.argv[1] === fileURLToPath(import.meta.url)) {
try {
process.exit(main());
} catch (e) {
console.error(`Error: ${e.message}`);
process.exit(1);
}
}