github-search-cli 9.3.0 → 9.4.1
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 +238 -0
- package/README.md +28 -52
- package/bin/ghfind.js +55 -11
- package/dist/cli.js +787 -145
- package/package.json +76 -72
- package/release-notes/9.3.0.md +29 -0
- package/scripts/postinstall.js +82 -15
- package/src/bookmarks.ts +13 -0
- package/src/cli.ts +240 -51
- package/src/doctor.ts +287 -0
- package/src/error-report.ts +157 -0
- package/src/help.ts +17 -2
- package/src/landing.ts +104 -0
- package/src/notifications.ts +2 -1
- package/src/releases.ts +144 -0
- package/src/tips.ts +22 -7
- package/src/toast.ts +69 -0
- package/src/tui/overlays/help.ts +19 -0
- package/src/tui/overlays/registry.ts +52 -0
- package/src/tui.ts +38 -1
- package/src/types.ts +3 -1
- package/src/update-check.ts +53 -0
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Prevent tui.ts auto-launch when imported from cli.ts
|
|
2
2
|
process.env.GHFIND_CLI_RUN = "1";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
parseQuery,
|
|
5
|
+
applyFlagFilters,
|
|
6
|
+
rankRepos,
|
|
7
|
+
createGitHubSearch,
|
|
8
|
+
createTrendingSearch,
|
|
9
|
+
} from "./search";
|
|
4
10
|
import {
|
|
5
11
|
createPackageSearch,
|
|
6
12
|
formatPackagesJson,
|
|
@@ -12,8 +18,18 @@ import {
|
|
|
12
18
|
type PackageSortMode,
|
|
13
19
|
} from "./package";
|
|
14
20
|
import { launchPackageBrowser } from "./package-browser";
|
|
15
|
-
import {
|
|
21
|
+
import { runDoctor } from "./doctor";
|
|
22
|
+
import { reportError } from "./error-report";
|
|
23
|
+
import {
|
|
24
|
+
format as formatOutput,
|
|
25
|
+
exportToFile,
|
|
26
|
+
pipeExec,
|
|
27
|
+
type Format,
|
|
28
|
+
type ExportFormat,
|
|
29
|
+
type FormatLine,
|
|
30
|
+
} from "./output";
|
|
16
31
|
import { runWatch } from "./watch";
|
|
32
|
+
import { checkAndNotify, allCachedReleases } from "./releases";
|
|
17
33
|
import { runInitWizard } from "./init";
|
|
18
34
|
import { buildComparisonTable } from "./compare";
|
|
19
35
|
import type { Repo } from "./types";
|
|
@@ -40,6 +56,7 @@ interface CLIFlags {
|
|
|
40
56
|
pipe?: string;
|
|
41
57
|
format?: string;
|
|
42
58
|
watch: boolean;
|
|
59
|
+
releases: boolean;
|
|
43
60
|
interval: number;
|
|
44
61
|
completion?: string;
|
|
45
62
|
init: boolean;
|
|
@@ -48,7 +65,8 @@ interface CLIFlags {
|
|
|
48
65
|
pkg: boolean;
|
|
49
66
|
theme?: string;
|
|
50
67
|
registry: string;
|
|
51
|
-
compare: string[];
|
|
68
|
+
compare: string[]; // repo fullNames to compare
|
|
69
|
+
doctor: boolean;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
function parseArgs(args: string[]): CLIFlags {
|
|
@@ -64,6 +82,7 @@ function parseArgs(args: string[]): CLIFlags {
|
|
|
64
82
|
trending: false,
|
|
65
83
|
since: "daily",
|
|
66
84
|
watch: false,
|
|
85
|
+
releases: false,
|
|
67
86
|
interval: 300,
|
|
68
87
|
init: false,
|
|
69
88
|
version: false,
|
|
@@ -71,21 +90,49 @@ function parseArgs(args: string[]): CLIFlags {
|
|
|
71
90
|
pkg: false,
|
|
72
91
|
registry: "npm",
|
|
73
92
|
compare: [],
|
|
93
|
+
doctor: false,
|
|
74
94
|
};
|
|
75
95
|
|
|
76
96
|
let queryParts: string[] = [];
|
|
77
97
|
for (let i = 0; i < args.length; i++) {
|
|
78
98
|
const arg = args[i];
|
|
79
99
|
switch (arg) {
|
|
80
|
-
case "--json":
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
case "--
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case "--
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
case "--json":
|
|
101
|
+
flags.json = true;
|
|
102
|
+
break;
|
|
103
|
+
case "--csv":
|
|
104
|
+
flags.csv = true;
|
|
105
|
+
break;
|
|
106
|
+
case "--markdown":
|
|
107
|
+
flags.markdown = true;
|
|
108
|
+
break;
|
|
109
|
+
case "--count":
|
|
110
|
+
flags.count = true;
|
|
111
|
+
break;
|
|
112
|
+
case "--trending":
|
|
113
|
+
flags.trending = true;
|
|
114
|
+
break;
|
|
115
|
+
case "--watch":
|
|
116
|
+
flags.watch = true;
|
|
117
|
+
break;
|
|
118
|
+
case "--releases":
|
|
119
|
+
flags.releases = true;
|
|
120
|
+
break;
|
|
121
|
+
case "--version":
|
|
122
|
+
case "-v":
|
|
123
|
+
flags.version = true;
|
|
124
|
+
break;
|
|
125
|
+
case "--doctor":
|
|
126
|
+
case "doctor":
|
|
127
|
+
flags.doctor = true;
|
|
128
|
+
break;
|
|
129
|
+
case "--help":
|
|
130
|
+
case "-h":
|
|
131
|
+
flags.help = true;
|
|
132
|
+
break;
|
|
133
|
+
case "init":
|
|
134
|
+
flags.init = true;
|
|
135
|
+
break;
|
|
89
136
|
case "--limit": {
|
|
90
137
|
const parsed = parseInt(args[++i]);
|
|
91
138
|
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
@@ -96,14 +143,30 @@ function parseArgs(args: string[]): CLIFlags {
|
|
|
96
143
|
}
|
|
97
144
|
break;
|
|
98
145
|
}
|
|
99
|
-
case "--sort":
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
case "--
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
case "--
|
|
106
|
-
|
|
146
|
+
case "--sort":
|
|
147
|
+
flags.sort = args[++i] as SearchOptions["sort"];
|
|
148
|
+
break;
|
|
149
|
+
case "--token":
|
|
150
|
+
flags.token = args[++i];
|
|
151
|
+
break;
|
|
152
|
+
case "--since":
|
|
153
|
+
flags.since = args[++i];
|
|
154
|
+
break;
|
|
155
|
+
case "--pipe":
|
|
156
|
+
flags.pipe = args[++i];
|
|
157
|
+
break;
|
|
158
|
+
case "--format":
|
|
159
|
+
flags.format = args[++i];
|
|
160
|
+
break;
|
|
161
|
+
case "--registry":
|
|
162
|
+
flags.registry = args[++i] ?? "npm";
|
|
163
|
+
break;
|
|
164
|
+
case "--theme":
|
|
165
|
+
flags.theme = args[++i] ?? "tokyo-night";
|
|
166
|
+
break;
|
|
167
|
+
case "--completion":
|
|
168
|
+
flags.completion = args[++i];
|
|
169
|
+
break;
|
|
107
170
|
case "--compare": {
|
|
108
171
|
// Collect repo names until next flag
|
|
109
172
|
const repos: string[] = [];
|
|
@@ -130,11 +193,19 @@ async function main() {
|
|
|
130
193
|
|
|
131
194
|
// Version
|
|
132
195
|
if (flags.version) {
|
|
133
|
-
const pkg = JSON.parse(
|
|
196
|
+
const pkg = JSON.parse(
|
|
197
|
+
readFileSync(join(PKG_DIR, "package.json"), "utf-8"),
|
|
198
|
+
);
|
|
134
199
|
console.log(`ghfind v${pkg.version}`);
|
|
135
200
|
return;
|
|
136
201
|
}
|
|
137
202
|
|
|
203
|
+
// Doctor — environment diagnostics
|
|
204
|
+
if (flags.doctor) {
|
|
205
|
+
await runDoctor();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
138
209
|
// Help
|
|
139
210
|
if (flags.help) {
|
|
140
211
|
console.log(`
|
|
@@ -149,6 +220,7 @@ Usage:
|
|
|
149
220
|
ghfind <query> --format <fmt> Format lines (urls|names|ssh-urls|clone-commands|ids)
|
|
150
221
|
ghfind <query> --pipe <target> Pipe to clone/open
|
|
151
222
|
ghfind --trending --json Trending repos as JSON
|
|
223
|
+
ghfind --releases Check bookmarks for new releases
|
|
152
224
|
ghfind --compare <repo1> <repo2> Compare two+ repos side-by-side
|
|
153
225
|
ghfind pkg <query> --json Search npm packages, output JSON
|
|
154
226
|
ghfind pkg <query> Search npm packages, text list
|
|
@@ -156,6 +228,7 @@ Usage:
|
|
|
156
228
|
ghfind init Run setup wizard
|
|
157
229
|
ghfind --completion <shell> Print completion script (bash|zsh|fish)
|
|
158
230
|
ghfind --version Print version
|
|
231
|
+
ghfind --doctor Run environment diagnostics (troubleshooting)
|
|
159
232
|
ghfind --help Print this help
|
|
160
233
|
|
|
161
234
|
Options:
|
|
@@ -170,6 +243,7 @@ Options:
|
|
|
170
243
|
--registry <name> Registry for pkg: npm (default) | others TBD
|
|
171
244
|
|
|
172
245
|
--watch Watch mode (re-run periodically)
|
|
246
|
+
--releases New-release feed for bookmarks
|
|
173
247
|
--interval <s> Watch interval in seconds (default: 300)
|
|
174
248
|
--completion <shell> Generate completions
|
|
175
249
|
`);
|
|
@@ -180,7 +254,10 @@ Options:
|
|
|
180
254
|
if (flags.completion) {
|
|
181
255
|
const shell = flags.completion;
|
|
182
256
|
try {
|
|
183
|
-
const content = readFileSync(
|
|
257
|
+
const content = readFileSync(
|
|
258
|
+
join(PKG_DIR, "completions", `ghfind.${shell}`),
|
|
259
|
+
"utf-8",
|
|
260
|
+
);
|
|
184
261
|
console.log(content);
|
|
185
262
|
} catch {
|
|
186
263
|
console.error(`Completions not available for shell: ${shell}`);
|
|
@@ -197,7 +274,12 @@ Options:
|
|
|
197
274
|
// Package search (prototype, ticket 002-008)
|
|
198
275
|
if (flags.pkg) {
|
|
199
276
|
const interactive =
|
|
200
|
-
!flags.query &&
|
|
277
|
+
!flags.query &&
|
|
278
|
+
!flags.json &&
|
|
279
|
+
!flags.csv &&
|
|
280
|
+
!flags.markdown &&
|
|
281
|
+
!flags.count &&
|
|
282
|
+
!flags.format;
|
|
201
283
|
if (interactive) {
|
|
202
284
|
await launchPackageBrowser();
|
|
203
285
|
} else {
|
|
@@ -207,12 +289,27 @@ Options:
|
|
|
207
289
|
}
|
|
208
290
|
|
|
209
291
|
// Determine output format
|
|
210
|
-
const outputFormat: ExportFormat | undefined =
|
|
211
|
-
|
|
212
|
-
|
|
292
|
+
const outputFormat: ExportFormat | undefined = flags.json
|
|
293
|
+
? "json"
|
|
294
|
+
: flags.csv
|
|
295
|
+
? "csv"
|
|
296
|
+
: flags.markdown
|
|
297
|
+
? "markdown"
|
|
298
|
+
: undefined;
|
|
299
|
+
const isNonInteractive =
|
|
300
|
+
outputFormat ||
|
|
301
|
+
flags.count ||
|
|
302
|
+
flags.format ||
|
|
303
|
+
flags.pipe ||
|
|
304
|
+
flags.watch ||
|
|
305
|
+
flags.releases;
|
|
213
306
|
|
|
214
307
|
// Non-interactive mode
|
|
215
308
|
if (isNonInteractive) {
|
|
309
|
+
if (flags.releases) {
|
|
310
|
+
await runReleases(flags, outputFormat);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
216
313
|
await runNonInteractive(flags, outputFormat);
|
|
217
314
|
return;
|
|
218
315
|
}
|
|
@@ -241,20 +338,32 @@ async function runPackageSearch(flags: CLIFlags): Promise<void> {
|
|
|
241
338
|
console.error(`Registry "${flags.registry}" not yet supported (only npm).`);
|
|
242
339
|
process.exit(1);
|
|
243
340
|
}
|
|
244
|
-
const PKG_SORTS: PackageSortMode[] = [
|
|
341
|
+
const PKG_SORTS: PackageSortMode[] = [
|
|
342
|
+
"best-match",
|
|
343
|
+
"score",
|
|
344
|
+
"downloads",
|
|
345
|
+
"name",
|
|
346
|
+
];
|
|
245
347
|
const pkgSort = flags.sort as PackageSortMode;
|
|
246
348
|
if (!PKG_SORTS.includes(pkgSort)) {
|
|
247
|
-
console.error(
|
|
349
|
+
console.error(
|
|
350
|
+
`Invalid sort for pkg: "${flags.sort}" (use best-match|score|downloads|name).`,
|
|
351
|
+
);
|
|
248
352
|
process.exit(1);
|
|
249
353
|
}
|
|
250
354
|
const search = createPackageSearch();
|
|
251
355
|
const query = flags.query.trim();
|
|
252
356
|
if (!query) {
|
|
253
|
-
console.error(
|
|
357
|
+
console.error(
|
|
358
|
+
"Usage: ghfind pkg <query> [--json|--csv|--markdown|--count|--format urls|names] [--limit <n>]",
|
|
359
|
+
);
|
|
254
360
|
process.exit(1);
|
|
255
361
|
}
|
|
256
362
|
try {
|
|
257
|
-
const { totalCount, packages } = await search.searchPackage(
|
|
363
|
+
const { totalCount, packages } = await search.searchPackage(
|
|
364
|
+
query,
|
|
365
|
+
flags.limit,
|
|
366
|
+
);
|
|
258
367
|
const sorted = sortPackages(packages, pkgSort);
|
|
259
368
|
if (flags.count) {
|
|
260
369
|
console.log(totalCount);
|
|
@@ -270,7 +379,9 @@ async function runPackageSearch(flags: CLIFlags): Promise<void> {
|
|
|
270
379
|
console.log(formatPackagesNames(sorted));
|
|
271
380
|
} else {
|
|
272
381
|
for (const p of sorted) {
|
|
273
|
-
console.log(
|
|
382
|
+
console.log(
|
|
383
|
+
`${p.name}@${p.version} ↓ ${p.downloads.toLocaleString()} ${p.score.toFixed(2)} ${p.description ?? ""}`,
|
|
384
|
+
);
|
|
274
385
|
}
|
|
275
386
|
}
|
|
276
387
|
} catch (err) {
|
|
@@ -279,15 +390,45 @@ async function runPackageSearch(flags: CLIFlags): Promise<void> {
|
|
|
279
390
|
}
|
|
280
391
|
}
|
|
281
392
|
|
|
393
|
+
/** `ghfind --releases` — check bookmarks for new releases; print the cached feed, then mark all seen. */
|
|
394
|
+
async function runReleases(flags: CLIFlags, outputFormat?: ExportFormat): Promise<void> {
|
|
395
|
+
const results = await checkAndNotify({ token: flags.token });
|
|
396
|
+
const releases = allCachedReleases();
|
|
397
|
+
if (outputFormat === "json") {
|
|
398
|
+
console.log(JSON.stringify(releases.map((r) => ({
|
|
399
|
+
fullName: r.fullName, tag: r.tagName, name: r.name,
|
|
400
|
+
url: r.url, publishedAt: r.publishedAt, prerelease: r.prerelease,
|
|
401
|
+
})), null, 2));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const unseen = results.reduce((n, r) => n + r.newReleases.length, 0);
|
|
405
|
+
const errors = results.filter((r) => r.error).length;
|
|
406
|
+
console.log(`Checked ${results.length} bookmarked repos — ${unseen} new release${unseen === 1 ? "" : "s"}${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}.`);
|
|
407
|
+
for (const r of releases) {
|
|
408
|
+
console.log(`${r.fullName} ${r.prerelease ? "[pre] " : ""}${r.tagName} ${r.publishedAt.slice(0, 10)}\n ${r.url}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
282
412
|
async function runNonInteractive(flags: CLIFlags, outputFormat?: ExportFormat) {
|
|
283
413
|
// Dispatch table: each handler receives shared context + flags, returns void.
|
|
284
414
|
const handlers: Record<string, (ctx: SearchContext) => Promise<void>> = {
|
|
285
415
|
trending: async (ctx) => {
|
|
286
|
-
const since =
|
|
416
|
+
const since =
|
|
417
|
+
flags.since === "weekly"
|
|
418
|
+
? "weekly"
|
|
419
|
+
: flags.since === "monthly"
|
|
420
|
+
? "monthly"
|
|
421
|
+
: "daily";
|
|
287
422
|
const trendingSearch = createTrendingSearch();
|
|
288
423
|
const response = await trendingSearch.search(
|
|
289
424
|
{ keywords: [], qualifiers: [], raw: "trending" },
|
|
290
|
-
{
|
|
425
|
+
{
|
|
426
|
+
limit: 25,
|
|
427
|
+
sort: "stars",
|
|
428
|
+
json: false,
|
|
429
|
+
verbose: false,
|
|
430
|
+
trendingSince: since,
|
|
431
|
+
},
|
|
291
432
|
);
|
|
292
433
|
if (outputFormat) {
|
|
293
434
|
console.log(formatOutput(response.repos, outputFormat));
|
|
@@ -295,21 +436,42 @@ async function runNonInteractive(flags: CLIFlags, outputFormat?: ExportFormat) {
|
|
|
295
436
|
console.log(response.totalCount);
|
|
296
437
|
} else {
|
|
297
438
|
for (const r of response.repos) {
|
|
298
|
-
console.log(
|
|
439
|
+
console.log(
|
|
440
|
+
r.fullName +
|
|
441
|
+
" ★ " +
|
|
442
|
+
r.stars +
|
|
443
|
+
" ▲ +" +
|
|
444
|
+
r.score +
|
|
445
|
+
" " +
|
|
446
|
+
since +
|
|
447
|
+
" ● " +
|
|
448
|
+
(r.language ?? ""),
|
|
449
|
+
);
|
|
299
450
|
}
|
|
300
451
|
}
|
|
301
452
|
},
|
|
302
453
|
watch: async (ctx) => {
|
|
303
454
|
let tick = 0;
|
|
304
455
|
await runWatch(
|
|
305
|
-
{
|
|
456
|
+
{
|
|
457
|
+
query: ctx.query,
|
|
458
|
+
sort: flags.sort,
|
|
459
|
+
limit: flags.limit,
|
|
460
|
+
token: flags.token,
|
|
461
|
+
intervalMs: flags.interval * 1000,
|
|
462
|
+
trending: flags.trending,
|
|
463
|
+
},
|
|
306
464
|
(repos, delta) => {
|
|
307
465
|
tick++;
|
|
308
466
|
if (outputFormat) {
|
|
309
|
-
console.log(
|
|
467
|
+
console.log(
|
|
468
|
+
`[Watch #${tick}] ${repos.length} results (${delta >= 0 ? "+" : ""}${delta} since last check)`,
|
|
469
|
+
);
|
|
310
470
|
console.log(formatOutput(repos, outputFormat));
|
|
311
471
|
} else {
|
|
312
|
-
console.log(
|
|
472
|
+
console.log(
|
|
473
|
+
`[Watch #${tick}] ${repos.length} results — ${delta >= 0 ? "+" : ""}${delta} since last check`,
|
|
474
|
+
);
|
|
313
475
|
}
|
|
314
476
|
},
|
|
315
477
|
(err) => console.error(`[Watch error] ${err.message}`),
|
|
@@ -317,21 +479,33 @@ async function runNonInteractive(flags: CLIFlags, outputFormat?: ExportFormat) {
|
|
|
317
479
|
},
|
|
318
480
|
pipe: async (ctx) => {
|
|
319
481
|
const response = await ctx.provider.search(ctx.parsed, {
|
|
320
|
-
limit: flags.limit,
|
|
482
|
+
limit: flags.limit,
|
|
483
|
+
sort: flags.sort,
|
|
484
|
+
json: false,
|
|
485
|
+
verbose: false,
|
|
486
|
+
token: flags.token,
|
|
321
487
|
});
|
|
322
488
|
const repos = rankRepos(response.repos, flags.sort);
|
|
323
489
|
await pipeExec(repos, flags.pipe!);
|
|
324
490
|
},
|
|
325
491
|
format: async (ctx) => {
|
|
326
492
|
const response = await ctx.provider.search(ctx.parsed, {
|
|
327
|
-
limit: flags.limit,
|
|
493
|
+
limit: flags.limit,
|
|
494
|
+
sort: flags.sort,
|
|
495
|
+
json: false,
|
|
496
|
+
verbose: false,
|
|
497
|
+
token: flags.token,
|
|
328
498
|
});
|
|
329
499
|
const repos = rankRepos(response.repos, flags.sort);
|
|
330
500
|
console.log(formatOutput(repos, flags.format as Format));
|
|
331
501
|
},
|
|
332
502
|
search: async (ctx) => {
|
|
333
503
|
const response = await ctx.provider.search(ctx.parsed, {
|
|
334
|
-
limit: flags.limit,
|
|
504
|
+
limit: flags.limit,
|
|
505
|
+
sort: flags.sort,
|
|
506
|
+
json: false,
|
|
507
|
+
verbose: false,
|
|
508
|
+
token: flags.token,
|
|
335
509
|
});
|
|
336
510
|
const repos = rankRepos(response.repos, flags.sort);
|
|
337
511
|
if (flags.count) {
|
|
@@ -348,7 +522,13 @@ async function runNonInteractive(flags: CLIFlags, outputFormat?: ExportFormat) {
|
|
|
348
522
|
for (const fullName of flags.compare) {
|
|
349
523
|
const res = await search.search(
|
|
350
524
|
{ keywords: [fullName], qualifiers: [], raw: fullName },
|
|
351
|
-
{
|
|
525
|
+
{
|
|
526
|
+
limit: 1,
|
|
527
|
+
sort: "stars",
|
|
528
|
+
json: false,
|
|
529
|
+
verbose: false,
|
|
530
|
+
token: flags.token,
|
|
531
|
+
},
|
|
352
532
|
);
|
|
353
533
|
repos.push(...res.repos);
|
|
354
534
|
}
|
|
@@ -357,21 +537,30 @@ async function runNonInteractive(flags: CLIFlags, outputFormat?: ExportFormat) {
|
|
|
357
537
|
};
|
|
358
538
|
|
|
359
539
|
// Determine which handler to run
|
|
360
|
-
const key =
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
540
|
+
const key =
|
|
541
|
+
flags.compare.length > 0
|
|
542
|
+
? "compare"
|
|
543
|
+
: flags.trending && !flags.watch
|
|
544
|
+
? "trending"
|
|
545
|
+
: flags.watch
|
|
546
|
+
? "watch"
|
|
547
|
+
: flags.pipe
|
|
548
|
+
? "pipe"
|
|
549
|
+
: flags.format
|
|
550
|
+
? "format"
|
|
551
|
+
: "search";
|
|
366
552
|
|
|
367
553
|
const ctx = buildSearchContext(flags);
|
|
368
554
|
await handlers[key](ctx);
|
|
369
555
|
}
|
|
370
556
|
|
|
371
|
-
|
|
372
|
-
|
|
373
557
|
main().catch((err) => {
|
|
374
|
-
|
|
375
|
-
|
|
558
|
+
if (err instanceof Error && err.name === "SearchCliError") {
|
|
559
|
+
// ponytail: known user-facing errors already carry a friendly message; skip the report prompt
|
|
560
|
+
console.error(err.userMessage ?? err.message);
|
|
561
|
+
} else {
|
|
562
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
563
|
+
reportError(err);
|
|
564
|
+
}
|
|
376
565
|
process.exit(1);
|
|
377
566
|
});
|