github-search-cli 0.8.0 → 0.8.2

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/src/types.ts CHANGED
@@ -1,140 +1,140 @@
1
- /** Core domain types shared across the CLI. */
2
-
3
- /** A single parsed search qualifier (e.g. `language:Rust`). */
4
- export interface Qualifier {
5
- /** The qualifier key, e.g. "language", "stars", "topic". */
6
- key: string;
7
- /** The qualifier value, e.g. "Rust", ">100". */
8
- value: string;
9
- /** True when the qualifier was negated (e.g. `-language:Rust`). */
10
- negated: boolean;
11
- }
12
-
13
- /** The fully parsed, normalized representation of a user's search request. */
14
- export interface ParsedQuery {
15
- /** Free-text keywords (everything that is not a qualifier). */
16
- keywords: string[];
17
- /** Structured qualifiers extracted from the query string. */
18
- qualifiers: Qualifier[];
19
- /** The original, untouched query string (preserved for debugging/display). */
20
- raw: string;
21
- }
22
-
23
- /** A repository as returned by a search provider, normalized to a stable shape. */
24
- export interface Repo {
25
- id: number;
26
- fullName: string; // owner/name
27
- name: string;
28
- owner: string;
29
- description: string | null;
30
- url: string;
31
- stars: number;
32
- forks: number;
33
- watchers: number;
34
- language: string | null;
35
- topics: string[];
36
- archived: boolean;
37
- isFork: boolean;
38
- private: boolean;
39
- createdAt: string; // ISO timestamp
40
- updatedAt: string; // ISO timestamp
41
- pushedAt: string; // ISO timestamp
42
- score: number; // relevance score returned by GitHub
43
- }
44
-
45
- /** Ranking strategies supported by the ranking layer. */
46
- export type SortStrategy = "best-match" | "stars" | "updated" | "forks";
47
-
48
- /** Options that control how a search is executed and presented. */
49
- export interface SearchOptions {
50
- /** Maximum number of results to return (pagination stops at this limit). */
51
- limit: number;
52
- /** Ranking strategy. */
53
- sort: SortStrategy;
54
- /** Emit machine-readable JSON instead of human-readable text. */
55
- json: boolean;
56
- /** GitHub API token (optional, raises rate limits). */
57
- token?: string;
58
- /** Page number for pagination (1-based, default 1). */
59
- page?: number;
60
- /** Verbose logging (outgoing query, errors, rate limits). */
61
- verbose: boolean;
62
- }
63
-
64
- /** A paginated response from a search provider. */
65
- export interface SearchResponse {
66
- totalCount: number;
67
- repos: Repo[];
68
- /** True when the provider hit a rate limit. */
69
- rateLimited: boolean;
70
- /** Remaining requests in the current rate-limit window, if known. */
71
- rateLimitRemaining?: number;
72
- }
73
-
74
- // ─── P1: Memory types ───────────────────────────────────────────────
75
-
76
- /** A single history entry (append-only log). */
77
- export interface HistoryEntry {
78
- query: string;
79
- mode: "search" | "trending";
80
- tab?: string;
81
- timestamp: number;
82
- resultCount: number;
83
- }
84
-
85
- /** A saved bookmark entry. */
86
- export interface Bookmark {
87
- repo: Repo;
88
- savedAt: number;
89
- tags: string[];
90
- notes?: string;
91
- }
92
-
93
- /** A named, saved search. */
94
- export interface SavedSearch {
95
- name: string;
96
- query: string;
97
- mode: "search" | "trending";
98
- tab?: string;
99
- sort: SortStrategy;
100
- limit: number;
101
- createdAt: number;
102
- lastRunAt?: number;
103
- }
104
-
105
- /** Session state restored on startup. */
106
- export interface SessionState {
107
- mode: "search" | "trending";
108
- query: string;
109
- sort: SortStrategy;
110
- limit: number;
111
- trendingTab: string;
112
- }
113
-
114
- /** Cache entry with TTL. */
115
- export interface CacheEntry<T> {
116
- data: T;
117
- cachedAt: number;
118
- ttlMs: number;
119
- }
120
-
121
- /** Application config, persisted to ~/.config/ghfind/config.json. */
122
- export interface Config {
123
- githubToken?: string;
124
- githubTokens?: string[];
125
- defaultSort: SortStrategy;
126
- defaultLimit: number;
127
- language?: string;
128
- theme: string;
129
- cacheTtlSeconds: number;
130
- defaultTab: "search" | "trending";
131
- historySize?: number;
132
- bookmarkFile?: string;
133
- }
134
-
135
- /** Abstract search provider. New sources (GitLab, local index, etc.) can
136
- * implement this interface without changing any other layer. */
137
- export interface SearchProvider {
138
- readonly name: string;
139
- search(query: ParsedQuery, options: SearchOptions): Promise<SearchResponse>;
1
+ /** Core domain types shared across the CLI. */
2
+
3
+ /** A single parsed search qualifier (e.g. `language:Rust`). */
4
+ export interface Qualifier {
5
+ /** The qualifier key, e.g. "language", "stars", "topic". */
6
+ key: string;
7
+ /** The qualifier value, e.g. "Rust", ">100". */
8
+ value: string;
9
+ /** True when the qualifier was negated (e.g. `-language:Rust`). */
10
+ negated: boolean;
11
+ }
12
+
13
+ /** The fully parsed, normalized representation of a user's search request. */
14
+ export interface ParsedQuery {
15
+ /** Free-text keywords (everything that is not a qualifier). */
16
+ keywords: string[];
17
+ /** Structured qualifiers extracted from the query string. */
18
+ qualifiers: Qualifier[];
19
+ /** The original, untouched query string (preserved for debugging/display). */
20
+ raw: string;
21
+ }
22
+
23
+ /** A repository as returned by a search provider, normalized to a stable shape. */
24
+ export interface Repo {
25
+ id: number;
26
+ fullName: string; // owner/name
27
+ name: string;
28
+ owner: string;
29
+ description: string | null;
30
+ url: string;
31
+ stars: number;
32
+ forks: number;
33
+ watchers: number;
34
+ language: string | null;
35
+ topics: string[];
36
+ archived: boolean;
37
+ isFork: boolean;
38
+ private: boolean;
39
+ createdAt: string; // ISO timestamp
40
+ updatedAt: string; // ISO timestamp
41
+ pushedAt: string; // ISO timestamp
42
+ score: number; // relevance score returned by GitHub
43
+ }
44
+
45
+ /** Ranking strategies supported by the ranking layer. */
46
+ export type SortStrategy = "best-match" | "stars" | "updated" | "forks";
47
+
48
+ /** Options that control how a search is executed and presented. */
49
+ export interface SearchOptions {
50
+ /** Maximum number of results to return (pagination stops at this limit). */
51
+ limit: number;
52
+ /** Ranking strategy. */
53
+ sort: SortStrategy;
54
+ /** Emit machine-readable JSON instead of human-readable text. */
55
+ json: boolean;
56
+ /** GitHub API token (optional, raises rate limits). */
57
+ token?: string;
58
+ /** Page number for pagination (1-based, default 1). */
59
+ page?: number;
60
+ /** Verbose logging (outgoing query, errors, rate limits). */
61
+ verbose: boolean;
62
+ }
63
+
64
+ /** A paginated response from a search provider. */
65
+ export interface SearchResponse {
66
+ totalCount: number;
67
+ repos: Repo[];
68
+ /** True when the provider hit a rate limit. */
69
+ rateLimited: boolean;
70
+ /** Remaining requests in the current rate-limit window, if known. */
71
+ rateLimitRemaining?: number;
72
+ }
73
+
74
+ // ─── P1: Memory types ───────────────────────────────────────────────
75
+
76
+ /** A single history entry (append-only log). */
77
+ export interface HistoryEntry {
78
+ query: string;
79
+ mode: "search" | "trending";
80
+ tab?: string;
81
+ timestamp: number;
82
+ resultCount: number;
83
+ }
84
+
85
+ /** A saved bookmark entry. */
86
+ export interface Bookmark {
87
+ repo: Repo;
88
+ savedAt: number;
89
+ tags: string[];
90
+ notes?: string;
91
+ }
92
+
93
+ /** A named, saved search. */
94
+ export interface SavedSearch {
95
+ name: string;
96
+ query: string;
97
+ mode: "search" | "trending";
98
+ tab?: string;
99
+ sort: SortStrategy;
100
+ limit: number;
101
+ createdAt: number;
102
+ lastRunAt?: number;
103
+ }
104
+
105
+ /** Session state restored on startup. */
106
+ export interface SessionState {
107
+ mode: "search" | "trending";
108
+ query: string;
109
+ sort: SortStrategy;
110
+ limit: number;
111
+ trendingTab: string;
112
+ }
113
+
114
+ /** Cache entry with TTL. */
115
+ export interface CacheEntry<T> {
116
+ data: T;
117
+ cachedAt: number;
118
+ ttlMs: number;
119
+ }
120
+
121
+ /** Application config, persisted to ~/.config/ghfind/config.json. */
122
+ export interface Config {
123
+ githubToken?: string;
124
+ githubTokens?: string[];
125
+ defaultSort: SortStrategy;
126
+ defaultLimit: number;
127
+ language?: string;
128
+ theme: string;
129
+ cacheTtlSeconds: number;
130
+ defaultTab: "search" | "trending";
131
+ historySize?: number;
132
+ bookmarkFile?: string;
133
+ }
134
+
135
+ /** Abstract search provider. New sources (GitLab, local index, etc.) can
136
+ * implement this interface without changing any other layer. */
137
+ export interface SearchProvider {
138
+ readonly name: string;
139
+ search(query: ParsedQuery, options: SearchOptions): Promise<SearchResponse>;
140
140
  }
package/src/watch.ts CHANGED
@@ -1,62 +1,62 @@
1
- /**
2
- * Watch mode — periodically re-run a search and show changes.
3
- *
4
- */
5
- import type { ParsedQuery, SearchOptions, Repo } from "./types";
6
- import { parseQuery, applyFlagFilters, rankRepos, createGitHubSearch } from "./search";
7
-
8
- export interface WatchOptions {
9
- query: string;
10
- sort: SearchOptions["sort"];
11
- limit: number;
12
- token?: string;
13
- intervalMs: number;
14
- trending?: boolean;
15
- }
16
-
17
- const MAX_FAILS = 5;
18
-
19
- /** Run watch mode. Calls onChange with each tick's results. */
20
- export async function runWatch(
21
- opts: WatchOptions,
22
- onChange: (repos: Repo[], delta: number) => void,
23
- onError: (err: Error) => void,
24
- ): Promise<void> {
25
- let previous: Repo[] = [];
26
- let failCount = 0;
27
- let intervalId: ReturnType<typeof setInterval> | null = null;
28
-
29
- const tick = async () => {
30
- try {
31
- const parsed = applyFlagFilters(parseQuery(opts.query), {});
32
- const provider = createGitHubSearch(undefined, opts.token ? [opts.token] : []);
33
- const options: SearchOptions = {
34
- limit: opts.limit,
35
- sort: opts.sort,
36
- json: false,
37
- verbose: false,
38
- token: opts.token,
39
- };
40
- const response = await provider.search(parsed, options);
41
- const repos = rankRepos(response.repos, options.sort);
42
-
43
- failCount = 0; // reset on success
44
- const delta = previous.length > 0 ? repos.length - previous.length : 0;
45
- previous = repos;
46
- onChange(repos, delta);
47
- } catch (err) {
48
- failCount++;
49
- onError(err instanceof Error ? err : new Error(String(err)));
50
- if (failCount >= MAX_FAILS) {
51
- onError(new Error(`Watch stopped after ${MAX_FAILS} consecutive failures`));
52
- if (intervalId) clearInterval(intervalId);
53
- }
54
- }
55
- };
56
-
57
- // Immediate first tick, then interval
58
- await tick();
59
- if (failCount < MAX_FAILS) {
60
- intervalId = setInterval(tick, opts.intervalMs);
61
- }
62
- }
1
+ /**
2
+ * Watch mode — periodically re-run a search and show changes.
3
+ *
4
+ */
5
+ import type { ParsedQuery, SearchOptions, Repo } from "./types";
6
+ import { parseQuery, applyFlagFilters, rankRepos, createGitHubSearch } from "./search";
7
+
8
+ export interface WatchOptions {
9
+ query: string;
10
+ sort: SearchOptions["sort"];
11
+ limit: number;
12
+ token?: string;
13
+ intervalMs: number;
14
+ trending?: boolean;
15
+ }
16
+
17
+ const MAX_FAILS = 5;
18
+
19
+ /** Run watch mode. Calls onChange with each tick's results. */
20
+ export async function runWatch(
21
+ opts: WatchOptions,
22
+ onChange: (repos: Repo[], delta: number) => void,
23
+ onError: (err: Error) => void,
24
+ ): Promise<void> {
25
+ let previous: Repo[] = [];
26
+ let failCount = 0;
27
+ let intervalId: ReturnType<typeof setInterval> | null = null;
28
+
29
+ const tick = async () => {
30
+ try {
31
+ const parsed = applyFlagFilters(parseQuery(opts.query), {});
32
+ const provider = createGitHubSearch(undefined, opts.token ? [opts.token] : []);
33
+ const options: SearchOptions = {
34
+ limit: opts.limit,
35
+ sort: opts.sort,
36
+ json: false,
37
+ verbose: false,
38
+ token: opts.token,
39
+ };
40
+ const response = await provider.search(parsed, options);
41
+ const repos = rankRepos(response.repos, options.sort);
42
+
43
+ failCount = 0; // reset on success
44
+ const delta = previous.length > 0 ? repos.length - previous.length : 0;
45
+ previous = repos;
46
+ onChange(repos, delta);
47
+ } catch (err) {
48
+ failCount++;
49
+ onError(err instanceof Error ? err : new Error(String(err)));
50
+ if (failCount >= MAX_FAILS) {
51
+ onError(new Error(`Watch stopped after ${MAX_FAILS} consecutive failures`));
52
+ if (intervalId) clearInterval(intervalId);
53
+ }
54
+ }
55
+ };
56
+
57
+ // Immediate first tick, then interval
58
+ await tick();
59
+ if (failCount < MAX_FAILS) {
60
+ intervalId = setInterval(tick, opts.intervalMs);
61
+ }
62
+ }