github-search-cli 0.6.1 → 0.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/README.md +145 -188
- package/bin/ghfind.js +14 -0
- package/completions/{search-cli.bash → ghfind.bash} +4 -4
- package/completions/ghfind.fish +19 -0
- package/completions/{search-cli.zsh → ghfind.zsh} +27 -27
- package/demo.svg +66 -0
- package/package.json +18 -9
- package/src/bookmarks.ts +69 -69
- package/src/cli.ts +239 -242
- package/src/compare.ts +46 -46
- package/src/config.ts +65 -66
- package/src/deepdive.ts +118 -124
- package/src/errors.ts +59 -62
- package/src/explore.ts +29 -45
- package/src/export.ts +63 -56
- package/src/help.ts +112 -46
- package/src/history.ts +91 -91
- package/src/init.ts +55 -47
- package/src/notifications.ts +65 -69
- package/src/open-url.ts +16 -0
- package/src/output.ts +67 -73
- package/src/pipe.ts +54 -65
- package/src/saved-searches.ts +58 -58
- package/src/search.ts +555 -0
- package/src/session.ts +36 -35
- package/src/share.ts +54 -49
- package/src/status.ts +61 -61
- package/src/themes.ts +52 -52
- package/src/tips.ts +27 -27
- package/src/trending.ts +556 -587
- package/src/tui.ts +1665 -1469
- package/src/types.ts +139 -147
- package/src/watch.ts +62 -53
- package/bin/search-cli.js +0 -17
- package/completions/search-cli.fish +0 -19
- package/src/cache.ts +0 -64
- package/src/normalizer.ts +0 -66
- package/src/provider.ts +0 -142
- package/src/query.ts +0 -251
- package/src/ranking.ts +0 -55
package/src/bookmarks.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bookmarks — saved repos with tags, persisted as JSON.
|
|
3
|
-
*/
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
import type { Bookmark, Repo } from "./types
|
|
7
|
-
import { stateDir } from "./config
|
|
8
|
-
|
|
9
|
-
const BOOKMARK_FILE = join(stateDir(), "bookmarks.json");
|
|
10
|
-
|
|
11
|
-
function ensureDir() {
|
|
12
|
-
mkdirSync(stateDir(), { recursive: true });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function loadRaw(): Bookmark[] {
|
|
16
|
-
ensureDir();
|
|
17
|
-
try {
|
|
18
|
-
const raw = readFileSync(BOOKMARK_FILE, "utf-8");
|
|
19
|
-
return JSON.parse(raw) as Bookmark[];
|
|
20
|
-
} catch {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function saveRaw(bookmarks: Bookmark[]): void {
|
|
26
|
-
ensureDir();
|
|
27
|
-
writeFileSync(BOOKMARK_FILE, JSON.stringify(bookmarks, null, 2));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Get all bookmarks, newest first. */
|
|
31
|
-
export function getBookmarks(): Bookmark[] {
|
|
32
|
-
return loadRaw().sort((a, b) => b.savedAt - a.savedAt);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Check if a repo is bookmarked by fullName. */
|
|
36
|
-
export function isBookmarked(fullName: string): boolean {
|
|
37
|
-
return loadRaw().some((b) => b.repo.fullName === fullName);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Toggle bookmark: add if not present, remove if present. Returns new state. */
|
|
41
|
-
export function toggleBookmark(repo: Repo, tags: string[] = []): boolean {
|
|
42
|
-
const bookmarks = loadRaw();
|
|
43
|
-
const idx = bookmarks.findIndex((b) => b.repo.fullName === repo.fullName);
|
|
44
|
-
if (idx >= 0) {
|
|
45
|
-
bookmarks.splice(idx, 1);
|
|
46
|
-
saveRaw(bookmarks);
|
|
47
|
-
return false; // removed
|
|
48
|
-
}
|
|
49
|
-
bookmarks.push({ repo, savedAt: Date.now(), tags });
|
|
50
|
-
saveRaw(bookmarks);
|
|
51
|
-
return true; // added
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Remove a bookmark by fullName. */
|
|
55
|
-
export function removeBookmark(fullName: string): void {
|
|
56
|
-
const bookmarks = loadRaw().filter((b) => b.repo.fullName !== fullName);
|
|
57
|
-
saveRaw(bookmarks);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Search bookmarks by text (matches name, description, tags). */
|
|
61
|
-
export function searchBookmarks(query: string): Bookmark[] {
|
|
62
|
-
const q = query.toLowerCase();
|
|
63
|
-
return getBookmarks().filter(
|
|
64
|
-
(b) =>
|
|
65
|
-
b.repo.fullName.toLowerCase().includes(q) ||
|
|
66
|
-
(b.repo.description ?? "").toLowerCase().includes(q) ||
|
|
67
|
-
b.tags.some((t) => t.toLowerCase().includes(q)),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bookmarks — saved repos with tags, persisted as JSON.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import type { Bookmark, Repo } from "./types";
|
|
7
|
+
import { stateDir } from "./config";
|
|
8
|
+
|
|
9
|
+
const BOOKMARK_FILE = join(stateDir(), "bookmarks.json");
|
|
10
|
+
|
|
11
|
+
function ensureDir() {
|
|
12
|
+
mkdirSync(stateDir(), { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function loadRaw(): Bookmark[] {
|
|
16
|
+
ensureDir();
|
|
17
|
+
try {
|
|
18
|
+
const raw = readFileSync(BOOKMARK_FILE, "utf-8");
|
|
19
|
+
return JSON.parse(raw) as Bookmark[];
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveRaw(bookmarks: Bookmark[]): void {
|
|
26
|
+
ensureDir();
|
|
27
|
+
writeFileSync(BOOKMARK_FILE, JSON.stringify(bookmarks, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Get all bookmarks, newest first. */
|
|
31
|
+
export function getBookmarks(): Bookmark[] {
|
|
32
|
+
return loadRaw().sort((a, b) => b.savedAt - a.savedAt);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Check if a repo is bookmarked by fullName. */
|
|
36
|
+
export function isBookmarked(fullName: string): boolean {
|
|
37
|
+
return loadRaw().some((b) => b.repo.fullName === fullName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Toggle bookmark: add if not present, remove if present. Returns new state. */
|
|
41
|
+
export function toggleBookmark(repo: Repo, tags: string[] = []): boolean {
|
|
42
|
+
const bookmarks = loadRaw();
|
|
43
|
+
const idx = bookmarks.findIndex((b) => b.repo.fullName === repo.fullName);
|
|
44
|
+
if (idx >= 0) {
|
|
45
|
+
bookmarks.splice(idx, 1);
|
|
46
|
+
saveRaw(bookmarks);
|
|
47
|
+
return false; // removed
|
|
48
|
+
}
|
|
49
|
+
bookmarks.push({ repo, savedAt: Date.now(), tags });
|
|
50
|
+
saveRaw(bookmarks);
|
|
51
|
+
return true; // added
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Remove a bookmark by fullName. */
|
|
55
|
+
export function removeBookmark(fullName: string): void {
|
|
56
|
+
const bookmarks = loadRaw().filter((b) => b.repo.fullName !== fullName);
|
|
57
|
+
saveRaw(bookmarks);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Search bookmarks by text (matches name, description, tags). */
|
|
61
|
+
export function searchBookmarks(query: string): Bookmark[] {
|
|
62
|
+
const q = query.toLowerCase();
|
|
63
|
+
return getBookmarks().filter(
|
|
64
|
+
(b) =>
|
|
65
|
+
b.repo.fullName.toLowerCase().includes(q) ||
|
|
66
|
+
(b.repo.description ?? "").toLowerCase().includes(q) ||
|
|
67
|
+
b.tags.some((t) => t.toLowerCase().includes(q)),
|
|
68
|
+
);
|
|
69
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,242 +1,239 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
case "--
|
|
81
|
-
case "
|
|
82
|
-
case "--
|
|
83
|
-
case "--
|
|
84
|
-
case "--
|
|
85
|
-
case "--
|
|
86
|
-
case "--
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
--
|
|
130
|
-
--
|
|
131
|
-
--
|
|
132
|
-
--
|
|
133
|
-
--
|
|
134
|
-
--
|
|
135
|
-
--
|
|
136
|
-
--
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
console.log(`[Watch #${tick}] ${repos.length} results
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
241
|
-
process.exit(1);
|
|
242
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** CLI entry point — routes between TUI browser and non-interactive modes. */
|
|
3
|
+
import { parseQuery, applyFlagFilters, rankRepos, createGitHubSearch } from "./search";
|
|
4
|
+
import { formatRepos, type ExportFormat } from "./export";
|
|
5
|
+
import { formatLines, pipeExec, type FormatLine } from "./pipe";
|
|
6
|
+
import { runWatch } from "./watch";
|
|
7
|
+
import { runInitWizard } from "./init";
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { launchBrowser } from "./tui";
|
|
12
|
+
import type { SearchOptions } from "./types";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const PKG_DIR = join(__dirname, "..");
|
|
16
|
+
|
|
17
|
+
interface CLIFlags {
|
|
18
|
+
query: string;
|
|
19
|
+
json: boolean;
|
|
20
|
+
csv: boolean;
|
|
21
|
+
markdown: boolean;
|
|
22
|
+
count: boolean;
|
|
23
|
+
limit: number;
|
|
24
|
+
sort: SearchOptions["sort"];
|
|
25
|
+
token?: string;
|
|
26
|
+
trending: boolean;
|
|
27
|
+
since: string;
|
|
28
|
+
pipe?: string;
|
|
29
|
+
format?: string;
|
|
30
|
+
watch: boolean;
|
|
31
|
+
interval: number;
|
|
32
|
+
completion?: string;
|
|
33
|
+
init: boolean;
|
|
34
|
+
version: boolean;
|
|
35
|
+
help: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseArgs(args: string[]): CLIFlags {
|
|
39
|
+
const flags: CLIFlags = {
|
|
40
|
+
query: "",
|
|
41
|
+
json: false,
|
|
42
|
+
csv: false,
|
|
43
|
+
markdown: false,
|
|
44
|
+
count: false,
|
|
45
|
+
limit: 50,
|
|
46
|
+
sort: "best-match",
|
|
47
|
+
token: process.env.GITHUB_TOKEN,
|
|
48
|
+
trending: false,
|
|
49
|
+
since: "daily",
|
|
50
|
+
watch: false,
|
|
51
|
+
interval: 300,
|
|
52
|
+
init: false,
|
|
53
|
+
version: false,
|
|
54
|
+
help: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
let queryParts: string[] = [];
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const arg = args[i];
|
|
60
|
+
switch (arg) {
|
|
61
|
+
case "--json": flags.json = true; break;
|
|
62
|
+
case "--csv": flags.csv = true; break;
|
|
63
|
+
case "--markdown": flags.markdown = true; break;
|
|
64
|
+
case "--count": flags.count = true; break;
|
|
65
|
+
case "--trending": flags.trending = true; break;
|
|
66
|
+
case "--watch": flags.watch = true; break;
|
|
67
|
+
case "--version": case "-v": flags.version = true; break;
|
|
68
|
+
case "--help": case "-h": flags.help = true; break;
|
|
69
|
+
case "init": flags.init = true; break;
|
|
70
|
+
case "--limit": {
|
|
71
|
+
const parsed = parseInt(args[++i]);
|
|
72
|
+
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
73
|
+
console.error(`Invalid limit: must be 1-100. Using default 50.`);
|
|
74
|
+
flags.limit = 50;
|
|
75
|
+
} else {
|
|
76
|
+
flags.limit = parsed;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "--sort": flags.sort = args[++i] as SearchOptions["sort"]; break;
|
|
81
|
+
case "--token": flags.token = args[++i]; break;
|
|
82
|
+
case "--since": flags.since = args[++i]; break;
|
|
83
|
+
case "--pipe": flags.pipe = args[++i]; break;
|
|
84
|
+
case "--format": flags.format = args[++i]; break;
|
|
85
|
+
case "--interval": flags.interval = parseInt(args[++i]) || 300; break;
|
|
86
|
+
case "--completion": flags.completion = args[++i]; break;
|
|
87
|
+
default:
|
|
88
|
+
if (!arg.startsWith("-")) queryParts.push(arg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
flags.query = queryParts.join(" ");
|
|
92
|
+
return flags;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main() {
|
|
96
|
+
const flags = parseArgs(process.argv.slice(2));
|
|
97
|
+
|
|
98
|
+
// Version
|
|
99
|
+
if (flags.version) {
|
|
100
|
+
const pkg = JSON.parse(readFileSync(join(PKG_DIR, "package.json"), "utf-8"));
|
|
101
|
+
console.log(`ghfind v${pkg.version}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Help
|
|
106
|
+
if (flags.help) {
|
|
107
|
+
console.log(`
|
|
108
|
+
ghfind — Interactive GitHub repository browser
|
|
109
|
+
|
|
110
|
+
Usage:
|
|
111
|
+
ghfind Launch interactive TUI
|
|
112
|
+
ghfind <query> --json Search, output JSON
|
|
113
|
+
ghfind <query> --csv Search, output CSV
|
|
114
|
+
ghfind <query> --markdown Search, output Markdown table
|
|
115
|
+
ghfind <query> --count Just the result count
|
|
116
|
+
ghfind <query> --format <fmt> Format lines (urls|names|ssh-urls|clone-commands|ids)
|
|
117
|
+
ghfind <query> --pipe <target> Pipe to clone/open
|
|
118
|
+
ghfind --trending --json Trending repos as JSON
|
|
119
|
+
ghfind --watch <query> Watch mode (poll every Ns)
|
|
120
|
+
ghfind init Run setup wizard
|
|
121
|
+
ghfind --completion <shell> Print completion script (bash|zsh|fish)
|
|
122
|
+
ghfind --version Print version
|
|
123
|
+
ghfind --help Print this help
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
--json, --csv, --markdown, --count Output format
|
|
127
|
+
--limit <n> Max results (default: 50)
|
|
128
|
+
--sort <strategy> Sort: best-match|stars|updated|forks
|
|
129
|
+
--token <token> GitHub API token
|
|
130
|
+
--trending Trending mode
|
|
131
|
+
--since <period> Trending period: daily|weekly|monthly
|
|
132
|
+
--pipe <target> Pipe target: clone|open
|
|
133
|
+
--format <fmt> Line format: urls|names|ssh-urls|...
|
|
134
|
+
--watch Watch mode (re-run periodically)
|
|
135
|
+
--interval <s> Watch interval in seconds (default: 300)
|
|
136
|
+
--completion <shell> Generate completions
|
|
137
|
+
`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Shell completions
|
|
142
|
+
if (flags.completion) {
|
|
143
|
+
const shell = flags.completion;
|
|
144
|
+
try {
|
|
145
|
+
const content = readFileSync(join(PKG_DIR, "completions", `ghfind.${shell}`), "utf-8");
|
|
146
|
+
console.log(content);
|
|
147
|
+
} catch {
|
|
148
|
+
console.error(`Completions not available for shell: ${shell}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Init wizard
|
|
155
|
+
if (flags.init) {
|
|
156
|
+
await runInitWizard();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Determine output format
|
|
161
|
+
const format: ExportFormat | undefined =
|
|
162
|
+
flags.json ? "json" : flags.csv ? "csv" : flags.markdown ? "markdown" : undefined;
|
|
163
|
+
const isNonInteractive = format || flags.count || flags.format || flags.pipe || flags.watch;
|
|
164
|
+
|
|
165
|
+
// Non-interactive mode
|
|
166
|
+
if (isNonInteractive) {
|
|
167
|
+
await runNonInteractive(flags, format);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Default: launch interactive TUI
|
|
172
|
+
await launchBrowser();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function runNonInteractive(flags: CLIFlags, format?: ExportFormat) {
|
|
176
|
+
const query = flags.query;
|
|
177
|
+
const provider = createGitHubSearch(undefined, flags.token ? [flags.token] : []);
|
|
178
|
+
|
|
179
|
+
// --watch mode
|
|
180
|
+
if (flags.watch) {
|
|
181
|
+
let tick = 0;
|
|
182
|
+
await runWatch(
|
|
183
|
+
{ query, sort: flags.sort, limit: flags.limit, token: flags.token, intervalMs: flags.interval * 1000, trending: flags.trending },
|
|
184
|
+
(repos, delta) => {
|
|
185
|
+
tick++;
|
|
186
|
+
if (format) {
|
|
187
|
+
console.log(`[Watch #${tick}] ${repos.length} results (${delta >= 0 ? "+" : ""}${delta} since last check)`);
|
|
188
|
+
console.log(formatRepos(repos, format));
|
|
189
|
+
} else {
|
|
190
|
+
console.log(`[Watch #${tick}] ${repos.length} results — ${delta >= 0 ? "+" : ""}${delta} since last check`);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
(err) => console.error(`[Watch error] ${err.message}`),
|
|
194
|
+
);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --pipe mode
|
|
199
|
+
if (flags.pipe) {
|
|
200
|
+
const parsed = applyFlagFilters(parseQuery(query), {});
|
|
201
|
+
const response = await provider.search(parsed, { limit: flags.limit, sort: flags.sort, json: false, verbose: false, token: flags.token });
|
|
202
|
+
const repos = rankRepos(response.repos, flags.sort);
|
|
203
|
+
await pipeExec(repos, flags.pipe);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --format mode
|
|
208
|
+
if (flags.format) {
|
|
209
|
+
const parsed = applyFlagFilters(parseQuery(query), {});
|
|
210
|
+
const response = await provider.search(parsed, { limit: flags.limit, sort: flags.sort, json: false, verbose: false, token: flags.token });
|
|
211
|
+
const repos = rankRepos(response.repos, flags.sort);
|
|
212
|
+
console.log(formatLines(repos, flags.format as FormatLine));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Standard search with format
|
|
217
|
+
const parsed = applyFlagFilters(parseQuery(query), {});
|
|
218
|
+
const response = await provider.search(parsed, {
|
|
219
|
+
limit: flags.limit, sort: flags.sort, json: false, verbose: false, token: flags.token,
|
|
220
|
+
});
|
|
221
|
+
const repos = rankRepos(response.repos, flags.sort);
|
|
222
|
+
|
|
223
|
+
// --count
|
|
224
|
+
if (flags.count) {
|
|
225
|
+
console.log(response.totalCount);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --json, --csv, --markdown
|
|
230
|
+
if (format) {
|
|
231
|
+
console.log(formatRepos(repos, format));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main().catch((err) => {
|
|
237
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|