github-search-cli 9.8.3 → 9.10.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 +123 -0
- package/README.md +68 -40
- package/dist/cli.js +992 -172
- package/docs/HOW-TO.md +255 -0
- package/package.json +3 -2
- package/release-notes/9.10.0.md +137 -0
- package/release-notes/9.9.0.md +193 -0
- package/src/cli.ts +13 -11
- package/src/errors.ts +35 -11
- package/src/help.ts +21 -4
- package/src/mcp-server.ts +18 -2
- package/src/search-state.ts +8 -10
- package/src/search.ts +683 -11
- package/src/trending.ts +2 -6
- package/src/tui/mouse.ts +349 -0
- package/src/tui.ts +433 -123
- package/src/types.ts +11 -1
- package/src/update-check.ts +72 -6
- package/src/watch.ts +16 -11
package/src/errors.ts
CHANGED
|
@@ -34,15 +34,21 @@ export class AuthError extends SearchCliError {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* HTTP 403
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* HTTP 403 that is NOT a primary rate limit (i.e. `x-ratelimit-remaining`
|
|
38
|
+
* was greater than zero). Three unrelated causes land here and the status
|
|
39
|
+
* code cannot tell them apart:
|
|
40
|
+
* - the token's org enforces SAML SSO and has not authorized it
|
|
41
|
+
* - a secondary/abuse rate limit (concurrent or rapid-fire requests)
|
|
42
|
+
* - a valid token blocked by org or resource restrictions
|
|
43
|
+
* The old message asserted SSO unconditionally, which sent people to fix a
|
|
44
|
+
* credential problem that did not exist. Check GHFIND_LOG for the body.
|
|
40
45
|
*/
|
|
41
46
|
export class ForbiddenError extends SearchCliError {
|
|
42
|
-
constructor() {
|
|
47
|
+
constructor(public readonly detail: string = "GitHub API returned 403") {
|
|
43
48
|
const msg =
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
`⚠ Forbidden (403) — ${detail}. Common causes: org SSO not authorizing the token, ` +
|
|
50
|
+
`or a secondary rate limit. Press [t] to change the token`;
|
|
51
|
+
super(msg, msg);
|
|
46
52
|
this.name = "ForbiddenError";
|
|
47
53
|
}
|
|
48
54
|
}
|
|
@@ -61,17 +67,35 @@ export class RateLimitError extends SearchCliError {
|
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export class BadQueryError extends SearchCliError {
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
/** A concrete, corrected query the user can retry with. */
|
|
71
|
+
constructor(
|
|
72
|
+
public readonly detail: string,
|
|
73
|
+
public readonly hint?: string,
|
|
74
|
+
) {
|
|
75
|
+
super(
|
|
76
|
+
`Invalid query: ${detail}`,
|
|
77
|
+
hint
|
|
78
|
+
? `⚠ Invalid query: ${detail}\n Try: ${hint}`
|
|
79
|
+
: `⚠ Invalid query: ${detail}`,
|
|
80
|
+
);
|
|
66
81
|
this.name = "BadQueryError";
|
|
67
82
|
}
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
export class NoResultsError extends SearchCliError {
|
|
71
|
-
|
|
86
|
+
/** Concrete loosened queries to retry, most promising first. */
|
|
87
|
+
constructor(
|
|
88
|
+
public readonly query: string,
|
|
89
|
+
public readonly relaxations: string[] = [],
|
|
90
|
+
) {
|
|
91
|
+
const tips = relaxations.length
|
|
92
|
+
? relaxations.map((r) => `Try: ${r}`).join("\n ")
|
|
93
|
+
: "Tips: check spelling, try fewer qualifiers, or [b]rowse trending";
|
|
72
94
|
super(
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
relaxations.length > 0
|
|
96
|
+
? `No results for "${query}" — try loosening: ${relaxations.join(" / ")}`
|
|
97
|
+
: `No results for "${query}"`,
|
|
98
|
+
`🔍 No results for "${query}".\n ${tips}`,
|
|
75
99
|
);
|
|
76
100
|
this.name = "NoResultsError";
|
|
77
101
|
}
|
package/src/help.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function buildHelpSections(): HelpSection[] {
|
|
|
30
30
|
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
31
31
|
{ keys: "Enter", action: "Open repo in browser" },
|
|
32
32
|
{ keys: "/", action: "Focus search input" },
|
|
33
|
-
{ keys: "
|
|
33
|
+
{ keys: "Esc", action: "Open command menu" },
|
|
34
34
|
{ keys: "? / Ctrl+H", action: "Toggle this help screen" },
|
|
35
35
|
{ keys: "q", action: "Quit" },
|
|
36
36
|
],
|
|
@@ -65,6 +65,7 @@ export function buildHelpSections(): HelpSection[] {
|
|
|
65
65
|
{ keys: "1-5", action: "Switch tab: Today → Week → Month → …" },
|
|
66
66
|
{ keys: "← → / h l", action: "Navigate tabs" },
|
|
67
67
|
],
|
|
68
|
+
note: "Tabs are also clickable.",
|
|
68
69
|
},
|
|
69
70
|
{
|
|
70
71
|
title: " Packages Mode ",
|
|
@@ -73,12 +74,12 @@ export function buildHelpSections(): HelpSection[] {
|
|
|
73
74
|
{ keys: "/", action: "Search npm packages" },
|
|
74
75
|
{ keys: "↑ ↓ / j k", action: "Move selection" },
|
|
75
76
|
{ keys: "Enter", action: "View package details" },
|
|
76
|
-
{ keys: "
|
|
77
|
+
{ keys: "Esc", action: "Open command menu" },
|
|
77
78
|
],
|
|
78
79
|
note: "Search by name, description, or tags. Sort by downloads or score.",
|
|
79
80
|
},
|
|
80
81
|
{
|
|
81
|
-
title: " Command Menu (
|
|
82
|
+
title: " Command Menu (Esc)",
|
|
82
83
|
titleColor: "purple",
|
|
83
84
|
rows: [
|
|
84
85
|
{ keys: "Sort", action: "Cycle sort: best-match → stars → …" },
|
|
@@ -103,7 +104,7 @@ export function buildHelpSections(): HelpSection[] {
|
|
|
103
104
|
{ keys: "History", action: "View search history" },
|
|
104
105
|
{ keys: "Help", action: "Show this help screen" },
|
|
105
106
|
],
|
|
106
|
-
note: "Navigate menu with ↑ ↓, select with Enter, back with ← / Esc",
|
|
107
|
+
note: "Navigate menu with ↑ ↓, select with Enter, back with ← / Esc. Clicking a row runs it directly. Esc (or the ☰ menu button) opens this menu; Space types a space in the query.",
|
|
107
108
|
},
|
|
108
109
|
{
|
|
109
110
|
title: " Bookmarks Page ",
|
|
@@ -116,6 +117,22 @@ export function buildHelpSections(): HelpSection[] {
|
|
|
116
117
|
],
|
|
117
118
|
note: "A full page, not an overlay — Esc returns to the mode menu.",
|
|
118
119
|
},
|
|
120
|
+
{
|
|
121
|
+
title: " Mouse ",
|
|
122
|
+
titleColor: "green",
|
|
123
|
+
rows: [
|
|
124
|
+
{ keys: "Click", action: "Select a row / card / tab" },
|
|
125
|
+
{ keys: "Double-click", action: "Open the repo or run the action" },
|
|
126
|
+
{ keys: "Scroll wheel", action: "Move the selection / scroll a pane" },
|
|
127
|
+
{ keys: "Click outside", action: "Dismiss an open overlay" },
|
|
128
|
+
{ keys: "Click query box", action: "Focus the search input" },
|
|
129
|
+
{
|
|
130
|
+
keys: "☰ menu button",
|
|
131
|
+
action: "Open the command menu (bottom-right)",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
note: "Every list is fully clickable; keyboard and mouse are interchangeable.",
|
|
135
|
+
},
|
|
119
136
|
{
|
|
120
137
|
title: " Overlay Keys ",
|
|
121
138
|
titleColor: "teal",
|
package/src/mcp-server.ts
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
createGitHubSearch,
|
|
30
30
|
createTrendingSearch,
|
|
31
31
|
resolveTrendingLanguage,
|
|
32
|
+
trendingQuery,
|
|
33
|
+
validateQuery,
|
|
32
34
|
} from "./search";
|
|
33
35
|
import {
|
|
34
36
|
createPackageSearch,
|
|
@@ -55,7 +57,7 @@ import {
|
|
|
55
57
|
type SkillSource,
|
|
56
58
|
} from "./skill-finder";
|
|
57
59
|
import { getVersion } from "./version";
|
|
58
|
-
import { RateLimitError } from "./errors";
|
|
60
|
+
import { RateLimitError, BadQueryError } from "./errors";
|
|
59
61
|
|
|
60
62
|
// ─── Protocol constants ────────────────────────────────────────────────
|
|
61
63
|
|
|
@@ -215,7 +217,7 @@ function asMcpToolError(err: unknown): McpToolError {
|
|
|
215
217
|
return new McpToolError(err instanceof Error ? err.message : String(err));
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
const TRENDING_QUERY =
|
|
220
|
+
const TRENDING_QUERY = trendingQuery();
|
|
219
221
|
|
|
220
222
|
// ─── Tool registry ─────────────────────────────────────────────────────
|
|
221
223
|
|
|
@@ -271,6 +273,20 @@ const TOOLS: ToolDefinition[] = [
|
|
|
271
273
|
|
|
272
274
|
const provider = createGitHubSearch(undefined, token ? [token] : []);
|
|
273
275
|
const parsed = parseQuery(query);
|
|
276
|
+
// Reject a malformed qualifier here rather than spending an API call:
|
|
277
|
+
// for an agent, GitHub's opaque 422 reads as breakage, whereas the
|
|
278
|
+
// specific message names the bad token and the fix.
|
|
279
|
+
try {
|
|
280
|
+
validateQuery(parsed);
|
|
281
|
+
} catch (err) {
|
|
282
|
+
if (err instanceof BadQueryError) {
|
|
283
|
+
return {
|
|
284
|
+
text: `Invalid query: ${err.detail}${err.hint ? ` Try: ${err.hint}` : ""}`,
|
|
285
|
+
data: { totalCount: 0, repos: [], invalidQuery: true },
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
274
290
|
const options: SearchOptions = {
|
|
275
291
|
limit,
|
|
276
292
|
sort,
|
package/src/search-state.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createGitHubSearch,
|
|
15
15
|
SearchModule,
|
|
16
16
|
TrendingAdapter,
|
|
17
|
+
trendingQuery,
|
|
17
18
|
} from "./search";
|
|
18
19
|
import { restoreSession, saveSession } from "./session";
|
|
19
20
|
import { appendHistory } from "./history";
|
|
@@ -155,16 +156,13 @@ export class SearchStateManager {
|
|
|
155
156
|
this.state.trendingTab = tab;
|
|
156
157
|
this.state.currentMode = "trending";
|
|
157
158
|
try {
|
|
158
|
-
const response = await this.trendingSearch.search(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
trendingSince: tabSince(tab),
|
|
166
|
-
},
|
|
167
|
-
);
|
|
159
|
+
const response = await this.trendingSearch.search(trendingQuery(), {
|
|
160
|
+
limit: 25,
|
|
161
|
+
sort: "stars",
|
|
162
|
+
json: false,
|
|
163
|
+
verbose: false,
|
|
164
|
+
trendingSince: tabSince(tab),
|
|
165
|
+
});
|
|
168
166
|
this.state.currentRepos = response.repos;
|
|
169
167
|
this.state.totalCount = response.totalCount;
|
|
170
168
|
appendHistory({
|