github-search-cli 9.6.0 → 9.7.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 +42 -0
- package/README.md +25 -21
- package/completions/ghfind.bash +5 -1
- package/completions/ghfind.fish +2 -1
- package/completions/ghfind.zsh +3 -0
- package/dist/cli.js +363 -149
- package/package.json +1 -1
- package/release-notes/9.7.0.md +53 -0
- package/src/agent-skill.ts +7 -6
- package/src/cli.ts +119 -4
- package/src/compare.ts +76 -1
- package/src/deepdive.ts +195 -23
- package/src/mcp-server.ts +26 -55
- package/src/search.ts +19 -0
package/src/mcp-server.ts
CHANGED
|
@@ -27,8 +27,7 @@ import {
|
|
|
27
27
|
rankRepos,
|
|
28
28
|
createGitHubSearch,
|
|
29
29
|
createTrendingSearch,
|
|
30
|
-
|
|
31
|
-
trendingLanguageSlug,
|
|
30
|
+
resolveTrendingLanguage,
|
|
32
31
|
} from "./search";
|
|
33
32
|
import {
|
|
34
33
|
createPackageSearch,
|
|
@@ -37,7 +36,11 @@ import {
|
|
|
37
36
|
} from "./package";
|
|
38
37
|
import { fetchOrgProfile, formatOrgText } from "./org";
|
|
39
38
|
import { fetchUserProfile, formatUserJson } from "./user";
|
|
40
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
fetchDeepDive,
|
|
41
|
+
buildDeepDiveText,
|
|
42
|
+
resolveRepoFromRef,
|
|
43
|
+
} from "./deepdive";
|
|
41
44
|
import { buildComparisonTable } from "./compare";
|
|
42
45
|
import { allCachedReleases } from "./releases";
|
|
43
46
|
import { getBookmarks } from "./bookmarks";
|
|
@@ -199,45 +202,9 @@ function formatPackageLines(
|
|
|
199
202
|
.join("\n");
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const fullName = input
|
|
206
|
-
.trim()
|
|
207
|
-
.replace(/\.git$/, "")
|
|
208
|
-
.replace(/^@/, "");
|
|
209
|
-
if (!REPO_NAME_RE.test(fullName)) {
|
|
210
|
-
throw new McpToolError(
|
|
211
|
-
`"${input}" is not a repo name — use owner/name, e.g. facebook/react.`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
const [owner, name] = fullName.split("/");
|
|
215
|
-
return { owner, name };
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Minimal Repo for deep-dive (fetchDeepDive only reads owner/name). */
|
|
219
|
-
function stubRepo(fullName: string): Repo {
|
|
220
|
-
const { owner, name } = parseRepoName(fullName);
|
|
221
|
-
return {
|
|
222
|
-
id: 0,
|
|
223
|
-
fullName: `${owner}/${name}`,
|
|
224
|
-
name,
|
|
225
|
-
owner,
|
|
226
|
-
description: null,
|
|
227
|
-
url: `https://github.com/${owner}/${name}`,
|
|
228
|
-
stars: 0,
|
|
229
|
-
forks: 0,
|
|
230
|
-
watchers: 0,
|
|
231
|
-
language: null,
|
|
232
|
-
topics: [],
|
|
233
|
-
archived: false,
|
|
234
|
-
isFork: false,
|
|
235
|
-
private: false,
|
|
236
|
-
createdAt: "",
|
|
237
|
-
updatedAt: "",
|
|
238
|
-
pushedAt: "",
|
|
239
|
-
score: 0,
|
|
240
|
-
};
|
|
205
|
+
/** Wrap a domain error as McpToolError so its message surfaces verbatim. */
|
|
206
|
+
function asMcpToolError(err: unknown): McpToolError {
|
|
207
|
+
return new McpToolError(err instanceof Error ? err.message : String(err));
|
|
241
208
|
}
|
|
242
209
|
|
|
243
210
|
const TRENDING_QUERY = { keywords: [], qualifiers: [], raw: "trending" };
|
|
@@ -366,16 +333,12 @@ const TOOLS: ToolDefinition[] = [
|
|
|
366
333
|
const token = resolveToken(state, args.token);
|
|
367
334
|
|
|
368
335
|
let language: string | undefined;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
`"${langArg}" is not a trending language filter. Try one of: rust, python, typescript, javascript, go, zig.`,
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
language = slug;
|
|
336
|
+
try {
|
|
337
|
+
language = resolveTrendingLanguage(
|
|
338
|
+
typeof args.language === "string" ? args.language : undefined,
|
|
339
|
+
);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
throw asMcpToolError(err);
|
|
379
342
|
}
|
|
380
343
|
|
|
381
344
|
const trending = createTrendingSearch();
|
|
@@ -582,7 +545,12 @@ const TOOLS: ToolDefinition[] = [
|
|
|
582
545
|
async run(args, state) {
|
|
583
546
|
const repo = String(args.repo ?? "").trim();
|
|
584
547
|
if (!repo) throw new McpToolError("repo must be a non-empty string.");
|
|
585
|
-
|
|
548
|
+
let stub: Repo;
|
|
549
|
+
try {
|
|
550
|
+
stub = await resolveRepoFromRef(repo, resolveToken(state, args.token));
|
|
551
|
+
} catch (err) {
|
|
552
|
+
throw asMcpToolError(err);
|
|
553
|
+
}
|
|
586
554
|
const data = await fetchDeepDive(stub, resolveToken(state, args.token));
|
|
587
555
|
return { text: buildDeepDiveText(data), data: { ...data } };
|
|
588
556
|
},
|
|
@@ -720,8 +688,11 @@ export async function processServerLine(
|
|
|
720
688
|
return;
|
|
721
689
|
}
|
|
722
690
|
|
|
723
|
-
|
|
724
|
-
|
|
691
|
+
// Extract the id into a const so TS narrows `undefined` out of it below
|
|
692
|
+
// (aliased conditional narrowing doesn't apply through the `let msg` bind).
|
|
693
|
+
const rawId = msg.id;
|
|
694
|
+
const isNotification = rawId === undefined || rawId === null;
|
|
695
|
+
const id: JsonRpcId = isNotification ? null : rawId;
|
|
725
696
|
|
|
726
697
|
// Notifications never get responses.
|
|
727
698
|
if (msg.method === "notifications/initialized") {
|
package/src/search.ts
CHANGED
|
@@ -570,6 +570,25 @@ export function trendingLanguageSlug(language: string): string {
|
|
|
570
570
|
return language.trim().toLowerCase().replace(/\s+/g, "-");
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Validate a user-supplied trending language and return its slug, or null if
|
|
575
|
+
* empty. Throws with a user-facing suggestion list for unknown languages.
|
|
576
|
+
* Shared by the MCP trending tool and the CLI trending filter.
|
|
577
|
+
*/
|
|
578
|
+
export function resolveTrendingLanguage(
|
|
579
|
+
language: string | undefined | null,
|
|
580
|
+
): string | undefined {
|
|
581
|
+
const trimmed = typeof language === "string" ? language.trim() : "";
|
|
582
|
+
if (!trimmed) return undefined;
|
|
583
|
+
const slug = trendingLanguageSlug(trimmed);
|
|
584
|
+
if (!TRENDING_LANGUAGES.has(slug)) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
`"${trimmed}" is not a trending language filter. Try one of: rust, python, typescript, javascript, go, zig.`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return slug;
|
|
590
|
+
}
|
|
591
|
+
|
|
573
592
|
/** Build the github.com/trending URL for a period + optional language filter. */
|
|
574
593
|
export function buildTrendingUrl(
|
|
575
594
|
since: "daily" | "weekly" | "monthly",
|